SynchronizationTool.py 43.7 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
40
from Publication import Publication, Subscriber
41
from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2
Nicolas Delaby's avatar
Nicolas Delaby committed
42
from Subscription import Subscription
Sebastien Robin's avatar
Sebastien Robin committed
43
from Products.ERP5Type import Permissions
Jean-Paul Smets's avatar
Jean-Paul Smets committed
44 45
from PublicationSynchronization import PublicationSynchronization
from SubscriptionSynchronization import SubscriptionSynchronization
46
from SyncCode import SyncCode
47
from Products.CMFCore.utils import getToolByName
48
from AccessControl.SecurityManagement import newSecurityManager
49
from AccessControl.SecurityManagement import noSecurityManager
50
from AccessControl.User import UnrestrictedUser
Sebastien Robin's avatar
Sebastien Robin committed
51
from Acquisition import aq_base
52
import urllib
53
import urllib2
54
import httplib
55
import socket
56
import os
Jean-Paul Smets's avatar
Jean-Paul Smets committed
57
import string
58 59
import commands
import random
60
from DateTime import DateTime
61
from zLOG import LOG, TRACE, DEBUG, INFO
Jean-Paul Smets's avatar
Jean-Paul Smets committed
62

63
from lxml import etree
64
parser = etree.XMLParser(remove_blank_text=True)
65

66 67 68 69 70 71 72 73 74 75 76
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)
77

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

79

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

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


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

93 94
  # On the server, this is use to keep track of the temporary
  # copies.
Nicolas Delaby's avatar
Nicolas Delaby committed
95 96
  objectsToRemove = []

Jean-Paul Smets's avatar
Jean-Paul Smets committed
97 98 99 100 101 102 103 104 105 106 107 108 109
  security = ClassSecurityInfo()

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

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

110 111 112 113
  # 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
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130

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

  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
143 144
               , 'manage_addPublicationForm' )
  manage_addPublicationForm = DTMLFile( 'dtml/manage_addPublication', globals() )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
145 146

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

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

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

  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.'
                    )

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

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

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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
263 264 265
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
297 298 299
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

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

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

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

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

347
  security.declareProtected(Permissions.ModifyPortalContent,
348
      'manage_syncSubscription')
349 350 351 352
  def manage_syncSubscription(self, title, RESPONSE=None):
    """
      reset a subscription
    """
353
    LOG('Synchronisation Subscription', INFO, 'Starting ...')
354
    self.SubSync(self.getSubscription(title).getPath())
355 356 357
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

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

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

380 381
  security.declareProtected(Permissions.AccessContentsInformation,
      'getObjectContainer')
382 383 384 385 386 387 388 389 390 391 392
  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

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

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


413 414
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSynchronizationList')
415
  def getSynchronizationList(self):
416 417 418 419 420
    """
      Returns the list of subscriptions and publications
    """
    return self.getSubscriptionList() + self.getPublicationList()

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

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

462 463
  security.declareProtected(Permissions.AccessContentsInformation,
      'getDocumentConflictList')
464 465 466 467 468 469 470 471
  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)


472 473
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSynchronizationState')
474
  def getSynchronizationState(self, context):
475
    """
476
    context : the context on which we are looking for state
477

478 479 480
    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.
481

482
    This method returns a mapping between subscription and states
Sebastien Robin's avatar
Sebastien Robin committed
483 484 485 486 487

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

526 527 528 529 530 531
  security.declareProtected(Permissions.AccessContentsInformation,
      'getAlertCodeList')
  def getAlertCodeList(self):
    return self.CODE_LIST

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

561
  security.declareProtected(Permissions.ModifyPortalContent,
562
      'applyPublisherDocument')
563 564 565 566 567 568 569
  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:
570
        #LOG('applyPublisherDocument, applying on conflict: ', DEBUG, conflict)
571 572
        c.applyPublisherValue()

573 574
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getPublisherDocumentPath')
575 576 577 578 579 580 581
  def getPublisherDocumentPath(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    subscriber = conflict.getSubscriber()
    return conflict.getObjectPath()

582 583
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getPublisherDocument')
584 585 586 587 588
  def getPublisherDocument(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    publisher_object_path = self.getPublisherDocumentPath(conflict)
589
    #LOG('getPublisherDocument publisher_object_path', TRACE, publisher_object_path)
590 591 592
    publisher_object = self.unrestrictedTraverse(publisher_object_path)
    return publisher_object

593 594 595 596 597 598 599 600 601 602 603 604 605

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

626 627
  def _getCopyId(self, object):
    directory = object.aq_inner.aq_parent
628
    if directory.getId() != 'portal_repository':
629 630 631 632 633 634 635 636 637
      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
638

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

671 672
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubscriberDocument')
673 674 675 676 677 678 679 680
  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

681 682
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applySubscriberDocument')
683 684 685 686 687 688 689 690 691
  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()

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

730
  security.declareProtected(Permissions.ModifyPortalContent,
731
      'managePublisherValue')
732
  def managePublisherValue(self, subscription_url, property_id, object_path,
733
      RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
734 735 736
    """
    Do whatever needed in order to store the local value on
    the remote server
Sebastien Robin's avatar
Sebastien Robin committed
737 738 739

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

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

  security.declareProtected(Permissions.ModifyPortalContent,
776
      'manageSubscriberDocument')
777 778 779 780
  def manageSubscriberDocument(self, subscription_url, object_path):
    """
    """
    for conflict in self.getConflictList():
781 782
      if '/'.join(conflict.getObjectPath()) == object_path:
        if conflict.getSubscriber().getSubscriptionUrl() == subscription_url:
783 784 785
          conflict.applySubscriberDocument()
          break
    self.managePublisherDocument(object_path)
786

787 788
  security.declareProtected(Permissions.ModifyPortalContent, 
      'managePublisherDocument')
789 790 791 792 793 794 795
  def managePublisherDocument(self, object_path):
    """
    """
    retry = True
    while retry:
      retry = False
      for conflict in self.getConflictList():
796
        if '/'.join(conflict.getObjectPath()) == object_path:
797 798 799
          conflict.applyPublisherDocument()
          retry = True
          break
Jean-Paul Smets's avatar
Jean-Paul Smets committed
800

801 802 803 804 805 806 807 808 809 810
  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
811
    elif isinstance(context, tuple):
812
      return context
Sebastien Robin's avatar
Sebastien Robin committed
813
    elif isinstance(context, tuple):
814 815 816 817
      return tuple(context.split('/'))
    else:
      return context.getPhysicalPath()

818
  security.declarePublic('sendResponse')
819
  def sendResponse(self, to_url=None, from_url=None, sync_id=None, xml=None,
820
      domain=None, send=1, content_type='application/vnd.syncml+xml'):
821 822 823 824
    """
    We will look at the url and we will see if we need to send mail, http
    response, or just copy to a file.
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
825 826 827 828 829
    #LOG('sendResponse, self.getPhysicalPath: ', INFO, self.getPhysicalPath())
    #LOG('sendResponse, to_url: ', INFO, to_url)
    #LOG('sendResponse, from_url: ', INFO, from_url)
    #LOG('sendResponse, sync_id: ', INFO, sync_id)
    #LOG('sendResponse, xml: \n', INFO, xml)
830 831
    if content_type == self.CONTENT_TYPE['SYNCML_WBXML']:
      xml = self.xml2wbxml(xml)
832
      #LOG('sendHttpResponse, xml after wbxml: \n', DEBUG, self.hexdump(xml))
833 834 835 836 837 838 839
    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()
840
        (status,output)=commands.getstatusoutput('gzip /tmp/%s' % filename)
841 842 843
        (status,output)=commands.getstatusoutput('gpg --yes --homedir \
            /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" -se \
            /tmp/%s.gz' % (gpg_key,filename))
844
        #LOG('readResponse, gpg output:', DEBUG, output)
845
        encrypted = file('/tmp/%s.gz.gpg' % filename,'r')
846 847
        xml = encrypted.read()
        encrypted.close()
848 849 850
        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
851
      if isinstance(to_url, str):
852
        if to_url.find('http://') == 0:
Sebastien Robin's avatar
Sebastien Robin committed
853
          domain = aq_base(domain)
854 855 856 857 858
          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
859
          #LOG('sendResponse, will start sendHttpResponse, xml', DEBUG, '')
860
          self.activate(activity='SQLQueue',
Nicolas Delaby's avatar
Nicolas Delaby committed
861 862 863 864 865 866 867
                        tag=domain.getId(),
                        priority=self.PRIORITY).sendHttpResponse(
                                              sync_id=sync_id,
                                              to_url=to_url,
                                              xml=xml,
                                              domain_path=domain.getPath(),
                                              content_type=content_type)
868
        elif to_url.find('file://') == 0:
869
          filename = to_url[len('file:/'):]
Nicolas Delaby's avatar
Nicolas Delaby committed
870
          stream = file(filename, 'w')
871 872 873
          stream.write(xml)
          stream.close()
          # we have to use local files (unit testing for example
874
        elif to_url.find('mailto:') == 0:
875 876 877
          # we will send an email
          to_address = to_url[len('mailto:'):]
          from_address = from_url[len('mailto:'):]
878
          self.sendMail(from_address, to_address, sync_id, xml)
879
    return xml
880 881

  security.declarePrivate('sendHttpResponse')
882
  def sendHttpResponse(self, to_url=None, sync_id=None, xml=None,
883
                       domain_path=None, content_type='application/vnd.syncml+xml'):
884
    domain = self.unrestrictedTraverse(domain_path)
885
    #LOG('sendHttpResponse, starting with domain:', DEBUG, domain)
886
    if domain is not None:
887
      if domain.domain_type == self.PUB and not domain.getActivityEnabled():
888
        return xml
889 890 891 892
    # Retrieve the proxy from os variables
    proxy_url = ''
    if os.environ.has_key('http_proxy'):
      proxy_url = os.environ['http_proxy']
893
    #LOG('sendHttpResponse, proxy_url:', DEBUG, proxy_url)
894 895 896 897 898 899 900
    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)
901 902
    opener = urllib2.build_opener(proxy_handler, proxy_auth_handler,
        auth_handler, TimeoutHTTPHandler)
903
    urllib2.install_opener(opener)
904
    to_encode = {}
905
    to_encode['text'] = xml
906
    to_encode['sync_id'] = sync_id
907 908
    headers = {'User-Agent':'ERP5SyncML', 'Content-Type':content_type}

909
    #XXX bad hack for synchronization with erp5
910 911 912 913 914 915 916 917 918 919
    # 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():
920
      #LOG('Synchronization with another ERP5 instance ...', DEBUG, '')
921 922 923
      if to_url.find('readResponse')<0:
        to_url = to_url + '/portal_synchronizations/readResponse'
      encoded = urllib.urlencode(to_encode)
924
      data = encoded
925 926
      request = urllib2.Request(url=to_url, data=data)
    else:
927 928
      #XXX only to synchronize with other server than erp5 (must be improved):
      data = head+xml
929 930
      request = urllib2.Request(to_url, data, headers)

931
    try:
932 933
      url_file = urllib2.urlopen(request)
      result = url_file.read()
934
    except socket.error, msg:
935
      self.activate(activity='SQLQueue',
Nicolas Delaby's avatar
Nicolas Delaby committed
936 937 938 939 940 941 942
                    tag=domain.getId(),
                    priority=self.PRIORITY).sendHttpResponse(
                                                  to_url=to_url,
                                                  sync_id=sync_id,
                                                  xml=xml,
                                                  domain_path=domain.getPath(),
                                                  content_type=content_type)
943
      LOG('sendHttpResponse, socket ERROR:', INFO, msg)
944
      LOG('sendHttpResponse, url, data', INFO, (to_url, data))
945
      return
946
    except urllib2.URLError, msg:
947 948
      LOG("sendHttpResponse, can't open url %s :" % to_url, INFO, msg)
      LOG('sendHttpResponse, to_url, data', INFO, (to_url, data))
949 950
      return

951
    if domain is not None:
952
      if domain.domain_type == self.SUB and not domain.getActivityEnabled():
953 954
        #if we don't use activity :
        gpg_key = domain.getGPGKey()
955
        if result:
956
          self.readResponse(sync_id=sync_id, text=result)
957
    return result
958 959 960 961 962 963 964

  security.declarePublic('sync')
  def sync(self):
    """
    This will try to synchronize every subscription
    """
    message_list = self.portal_activities.getMessageList()
965
    #LOG('sync, len(message_list):', DEBUG, len(message_list))
966 967
    if len(message_list) == 0:
      for subscription in self.getSubscriptionList():
968 969 970
        user_id = subscription.getZopeUser()
        uf = self.getPortalObject().acl_users
        user = uf.getUserById(user_id).__of__(uf)
Fabien Morin's avatar
Fabien Morin committed
971
        newSecurityManager(None, user)
972
        subscription.activate(activity='SQLQueue',
Nicolas Delaby's avatar
Nicolas Delaby committed
973 974
                              tag=subscription.getId(),
                              priority=self.PRIORITY
975
                                  ).SubSync(subscription.getPath())
976 977

  security.declarePublic('readResponse')
978
  def readResponse(self, text='', sync_id=None, to_url=None, from_url=None):
979 980 981 982
    """
    We will look at the url and we will see if we need to send mail, http
    response, or just copy to a file.
    """
983
    #LOG('readResponse, text :', DEBUG, text)
984
    #LOG('readResponse, hexdump(text) :', DEBUG, self.hexdump(text))
985
    status_code = None
986
    if text:
987 988 989 990
      # 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 = ''
991
      #LOG('readResponse, sync_id :', DEBUG, sync_id)
992
      for publication in self.getPublicationList():
993
        if publication.getTitle() == sync_id:
994
          gpg_key = publication.getGPGKey()
995
          domain = publication
996 997
          break
      if not gpg_key:
998
        for subscription in self.getSubscriptionList():
999
          if subscription.getTitle() == sync_id:
1000
            gpg_key = subscription.getGPGKey()
1001
            domain = subscription
Fabien Morin's avatar
Fabien Morin committed
1002
            user = domain.getZopeUser()
1003
            #LOG('readResponse, user :', DEBUG, user)
Fabien Morin's avatar
Fabien Morin committed
1004
            newSecurityManager(None, user)
1005
            break
1006
      # decrypt the message if needed
1007
      else:
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
        (status, output) = commands.getstatusoutput('gpg --homedir \
1013 1014
            /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
Nicolas Delaby's avatar
Nicolas Delaby committed
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 = etree.XML(text, parser=parser)
Sebastien Robin's avatar
Sebastien Robin committed
1030
      url = self.getTarget(xml)
1031
      for publication in self.getPublicationList():
1032
        if publication.getPublicationUrl() == url and \
1033
        publication.getTitle() == sync_id:
1034 1035
          if publication.getActivityEnabled():
            #use activities to send SyncML data.
1036
            publication.activate(activity='SQLQueue',
Nicolas Delaby's avatar
Nicolas Delaby committed
1037 1038
                                tag=publication.getId(),
                                priority=self.PRIORITY).PubSync(
1039 1040
                                                        publication.getPath(),
                                                        text)
1041 1042
            return ' '
          else:
1043
            result = self.PubSync(publication.getPath(), xml)
1044 1045
            # Then encrypt the message
            xml = result['xml']
1046 1047
            if publication.getSyncContentType() ==\
             self.CONTENT_TYPE['SYNCML_WBXML']:
1048
              xml = self.xml2wbxml(xml)
1049
            return xml
1050
      for subscription in self.getSubscriptionList():
1051 1052
        if subscription.getSubscriptionUrl() == url and \
            subscription.getTitle() == sync_id:
1053
              subscription_path = subscription.getPath()
1054
              self.activate(activity='SQLQueue',
Nicolas Delaby's avatar
Nicolas Delaby committed
1055 1056
                            tag=subscription.getId(),
                            priority=self.PRIORITY).SubSync(
1057 1058
                                                      subscription_path,
                                                      text)
1059
              return ' '
1060
    # we use from only if we have a file
Sebastien Robin's avatar
Sebastien Robin committed
1061
    elif isinstance(from_url, str):
1062
      if from_url.find('file://') == 0:
1063 1064
        try:
          filename = from_url[len('file:/'):]
1065
          stream = file(filename, 'r')
1066
          xml = stream.read()
1067
          #LOG('readResponse', DEBUG, 'file... msg: %s' % str(stream.read()))
1068
        except IOError:
1069
          LOG('readResponse, cannot read file: ', INFO, filename)
1070
          xml = None
1071
        if xml is not None and len(xml) == 0:
1072 1073
          xml = None
        return xml
1074

1075 1076
  security.declareProtected(Permissions.ModifyPortalContent, 
      'getPublicationIdFromTitle')
1077 1078 1079 1080 1081 1082
  def getPublicationIdFromTitle(self, title):
    """
    simply return an id from a title
    """
    return 'pub_' + title

1083 1084
  security.declareProtected(Permissions.ModifyPortalContent, 
      'getPublicationIdFromTitle')
1085 1086 1087 1088 1089 1090
  def getSubscriptionIdFromTitle(self, title):
    """
    simply return an id from a title
    """
    return 'sub_' + title

Sebastien Robin's avatar
Sebastien Robin committed
1091
  security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
1092
  def addNode(self, conduit='ERP5Conduit', **kw):
Sebastien Robin's avatar
Sebastien Robin committed
1093 1094 1095
    """
    """
    # Import the conduit and get it
1096
    conduit_object = self.getConduitByName(conduit)
Sebastien Robin's avatar
Sebastien Robin committed
1097 1098
    return conduit_object.addNode(**kw)

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