SynchronizationTool.py 44.1 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
## Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#          Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################

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

from OFS.SimpleItem import SimpleItem
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32
from Products.ERP5Type.Core.Folder import Folder
33
from Products.ERP5Type.Base import Base
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34 35 36 37 38
from Products.CMFCore.utils import UniqueObject
from Globals import InitializeClass, DTMLFile, PersistentMapping, Persistent
from AccessControl import ClassSecurityInfo, getSecurityManager
from Products.CMFCore import CMFCorePermissions
from Products.ERP5SyncML import _dtmldir
39
from Products.ERP5SyncML import Conduit
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 64
from lxml import etree

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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


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

476 477 478
    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.
479

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

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

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

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

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

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

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

591 592 593 594 595 596 597 598 599 600 601 602 603

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

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

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

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

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

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

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

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

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

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

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

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

815
  security.declarePublic('sendResponse')
816
  def sendResponse(self, to_url=None, from_url=None, sync_id=None, xml=None,
817
      domain=None, send=1, content_type='application/vnd.syncml+xml'):
818 819 820 821
    """
    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
822 823 824 825 826
    #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)
827 828
    if content_type == self.CONTENT_TYPE['SYNCML_WBXML']:
      xml = self.xml2wbxml(xml)
829
      #LOG('sendHttpResponse, xml after wbxml: \n', DEBUG, self.hexdump(xml))
Nicolas Delaby's avatar
Nicolas Delaby committed
830 831
    if isinstance(xml, unicode):
      xml = xml.encode('utf-8')
832 833 834 835 836 837 838
    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()
839
        (status,output)=commands.getstatusoutput('gzip /tmp/%s' % filename)
840 841 842
        (status,output)=commands.getstatusoutput('gpg --yes --homedir \
            /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" -se \
            /tmp/%s.gz' % (gpg_key,filename))
843
        #LOG('readResponse, gpg output:', DEBUG, output)
844
        encrypted = file('/tmp/%s.gz.gpg' % filename,'r')
845 846
        xml = encrypted.read()
        encrypted.close()
847 848 849
        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
850
      if isinstance(to_url, str):
851
        if to_url.find('http://') == 0:
Sebastien Robin's avatar
Sebastien Robin committed
852
          domain = aq_base(domain)
853 854 855 856 857
          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
858 859
          #LOG('sendResponse, will start sendHttpResponse, xml', DEBUG, '')
          activity = self.getActivityType(domain=domain)
860
          self.activate(activity=activity,
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 870 871 872 873
          filename = to_url[len('file:/'):]
          stream = file(filename,'w')
          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 905
    to_encode = {}
    head = '<?xml version="1.0" encoding="UTF-8"?>'
906 907 908 909 910 911

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

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

937
    try:
938 939
      url_file = urllib2.urlopen(request)
      result = url_file.read()
940
    except socket.error, msg:
941
      activity = self.getActivityType(domain=domain)
942
      self.activate(activity=activity,
Nicolas Delaby's avatar
Nicolas Delaby committed
943 944 945 946 947 948 949
                    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)
950
      LOG('sendHttpResponse, socket ERROR:', INFO, msg)
951
      LOG('sendHttpResponse, url, data', INFO, (to_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
      if domain.domain_type == self.SUB and not domain.getActivityEnabled():
960 961
        #if we don't use activity :
        gpg_key = domain.getGPGKey()
962
        if result:
963
          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():
975 976 977
        user_id = subscription.getZopeUser()
        uf = self.getPortalObject().acl_users
        user = uf.getUserById(user_id).__of__(uf)
Fabien Morin's avatar
Fabien Morin committed
978
        newSecurityManager(None, user)
979
        activity = self.getActivityType(domain=subscription)
980
        subscription.activate(activity=activity,
Nicolas Delaby's avatar
Nicolas Delaby committed
981 982
                              tag=subscription.getId(),
                              priority=self.PRIORITY
983
                                  ).SubSync(subscription.getPath())
984 985

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

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

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

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

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

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