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

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

from OFS.SimpleItem import SimpleItem
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32
from Products.ERP5Type.Core.Folder import Folder
33
from Products.ERP5Type.Base import Base
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34 35 36 37 38
from Products.CMFCore.utils import UniqueObject
from Globals import InitializeClass, DTMLFile, PersistentMapping, Persistent
from AccessControl import ClassSecurityInfo, getSecurityManager
from Products.CMFCore import CMFCorePermissions
from Products.ERP5SyncML import _dtmldir
39
from Products.ERP5SyncML import Conduit
Jean-Paul Smets's avatar
Jean-Paul Smets committed
40
from Publication import Publication,Subscriber
41
from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2
Jean-Paul Smets's avatar
Jean-Paul Smets committed
42
from Subscription import Subscription,Signature
43 44
from XMLSyncUtils import Parse
#from Ft.Xml import Parse
Sebastien Robin's avatar
Sebastien Robin committed
45
from Products.ERP5Type import Permissions
Jean-Paul Smets's avatar
Jean-Paul Smets committed
46 47
from PublicationSynchronization import PublicationSynchronization
from SubscriptionSynchronization import SubscriptionSynchronization
48
from Products.CMFCore.utils import getToolByName
49
from AccessControl.SecurityManagement import newSecurityManager
50
from AccessControl.SecurityManagement import noSecurityManager
51
from AccessControl.User import UnrestrictedUser
Sebastien Robin's avatar
Sebastien Robin committed
52
from Acquisition import aq_base
53
import urllib
54
import urllib2
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 zLOG import LOG
Jean-Paul Smets's avatar
Jean-Paul Smets committed
61

62

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

64 65
class SynchronizationTool( SubscriptionSynchronization, 
    PublicationSynchronization, UniqueObject, Folder):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
66 67
  """
    This tool implements the synchronization algorithm
Jean-Paul Smets's avatar
Jean-Paul Smets committed
68 69

    TODO: XXX-Please use BaseTool
Jean-Paul Smets's avatar
Jean-Paul Smets committed
70 71 72
  """


73
  id           = 'portal_synchronizations'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
74
  meta_type    = 'ERP5 Synchronizations'
75
  portal_type  = 'Synchronisation Tool'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
76

77 78 79 80
  # On the server, this is use to keep track of the temporary
  # copies.
  objectsToRemove = [] 
  
Jean-Paul Smets's avatar
Jean-Paul Smets committed
81 82 83 84 85 86 87 88 89 90 91 92 93
  security = ClassSecurityInfo()

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

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

94 95 96 97
  # 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
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114

  #
  #  ZMI methods
  #
  manage_options = ( ( { 'label'   : 'Overview'
             , 'action'   : 'manage_overview'
             }
            , { 'label'   : 'Publications'
             , 'action'   : 'managePublications'
             }
            , { 'label'   : 'Subscriptions'
             , 'action'   : 'manageSubscriptions'
             }
            , { 'label'   : 'Conflicts'
             , 'action'   : 'manageConflicts'
             }
            )
115
           + Folder.manage_options
Jean-Paul Smets's avatar
Jean-Paul Smets committed
116 117 118 119 120 121 122 123 124 125 126
           )

  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
127 128
               , 'manage_addPublicationForm' )
  manage_addPublicationForm = DTMLFile( 'dtml/manage_addPublication', globals() )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
129 130

  security.declareProtected( CMFCorePermissions.ManagePortal
Yoshinori Okuji's avatar
Yoshinori Okuji committed
131
               , 'manageSubscriptions' )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
132 133 134 135 136 137 138
  manageSubscriptions = DTMLFile( 'dtml/manageSubscriptions', globals() )

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

  security.declareProtected( CMFCorePermissions.ManagePortal
139 140
               , 'manage_addSubscriptionForm' )
  manage_addSubscriptionForm = DTMLFile( 'dtml/manage_addSubscription', globals() )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160

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

161 162
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_addPublication')
163
  def manage_addPublication(self, title, publication_url, destination_path,
164 165
            query, xml_mapping, conduit, gpg_key, 
            synchronization_id_generator=None, gid_generator=None, 
166
            media_type=None, auth_required=0, authentication_format='', 
167 168
            authentication_type='', RESPONSE=None):
    """ 
Jean-Paul Smets's avatar
Jean-Paul Smets committed
169 170
      create a new publication
    """
171 172 173
    #if not('publications' in self.objectIds()):
    #  publications = Folder('publications')
    #  self._setObject(publications.id, publications)
174
    folder = self.getObjectContainer()
175 176
    new_id = self.getPublicationIdFromTitle(title)
    pub = Publication(new_id, title, publication_url, destination_path,
177
                      query, xml_mapping, conduit, gpg_key, 
178
                      synchronization_id_generator, gid_generator, media_type, 
179
                      auth_required, authentication_format, authentication_type)
180
    folder._setObject( new_id, pub )
181 182 183
    #if len(self.list_publications) == 0:
    #  self.list_publications = PersistentMapping()
    #self.list_publications[id] = pub
Jean-Paul Smets's avatar
Jean-Paul Smets committed
184 185 186
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

187 188
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_addSubscription')
189
  def manage_addSubscription(self, title, publication_url, subscription_url,
190
                       destination_path, query, xml_mapping, conduit, gpg_key, 
191
                       synchronization_id_generator=None, gid_generator=None, 
192
                       media_type=None, login=None, password=None, 
193
                       RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
194
    """
Sebastien Robin's avatar
Sebastien Robin committed
195
      XXX should be renamed as addSubscription
Jean-Paul Smets's avatar
Jean-Paul Smets committed
196 197
      create a new subscription
    """
198 199 200
    #if not('subscriptions' in self.objectIds()):
    #  subscriptions = Folder('subscriptions')
    #  self._setObject(subscriptions.id, subscriptions)
201
    folder = self.getObjectContainer()
202 203
    new_id = self.getSubscriptionIdFromTitle(title)
    sub = Subscription(new_id, title, publication_url, subscription_url,
204
                       destination_path, query, xml_mapping, conduit, gpg_key,
205
                       synchronization_id_generator, gid_generator, media_type, 
206
                       login, password)
207
    folder._setObject( new_id, sub )
208 209 210
    #if len(self.list_subscriptions) == 0:
    #  self.list_subscriptions = PersistentMapping()
    #self.list_subscriptions[id] = sub
Jean-Paul Smets's avatar
Jean-Paul Smets committed
211 212 213
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

214 215
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_editPublication')
216
  def manage_editPublication(self, title, publication_url, destination_path,
217 218
                       query, xml_mapping, conduit, gpg_key, 
                       synchronization_id_generator, gid_generator, 
219
                       media_type=None, auth_required=0, 
220 221
                       authentication_format='', authentication_type='', 
                       RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
222 223 224
    """
      modify a publication
    """
225
    pub = self.getPublication(title)
226 227 228 229
    pub.setTitle(title)
    pub.setPublicationUrl(publication_url)
    pub.setDestinationPath(destination_path)
    pub.setQuery(query)
230
    pub.setConduit(conduit)
231 232
    pub.setXMLMapping(xml_mapping)
    pub.setGPGKey(gpg_key)
233
    pub.setSynchronizationIdGenerator(synchronization_id_generator)
234
    pub.setGidGenerator(gid_generator)
235
    pub.setMediaType(media_type)
236 237 238 239
    pub.setAuthentication(auth_required)
    pub.setAuthenticationFormat(authentication_format)
    pub.setAuthenticationType(authentication_type)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
240 241 242
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

243 244
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_editSubscription')
245
  def manage_editSubscription(self, title, publication_url, subscription_url,
246
      destination_path, query, xml_mapping, conduit, gpg_key, 
247
      synchronization_id_generator, gid_generator, media_type=None,login='', 
248
      password='', RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
249 250 251
    """
      modify a subscription
    """
252
    sub = self.getSubscription(title)
253 254 255 256
    sub.setTitle(title)
    sub.setPublicationUrl(publication_url)
    sub.setDestinationPath(destination_path)
    sub.setQuery(query)
257
    sub.setConduit(conduit)
258 259 260
    sub.setXMLMapping(xml_mapping)
    sub.setGPGKey(gpg_key)
    sub.setSubscriptionUrl(subscription_url)
261
    sub.setSynchronizationIdGenerator(synchronization_id_generator)
262
    sub.setGidGenerator(gid_generator)
263
    sub.setMediaType(media_type)
264 265
    sub.setLogin(login)
    sub.setPassword(password)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
266 267 268
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

269 270
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_deletePublication')
271
  def manage_deletePublication(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
272 273 274
    """
      delete a publication
    """
275
    id = self.getPublicationIdFromTitle(title)
276 277
    folder = self.getObjectContainer()
    folder._delObject(id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
278 279 280
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

281 282
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_deleteSubscription')
283
  def manage_deleteSubscription(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
284 285 286
    """
      delete a subscription
    """
287
    id = self.getSubscriptionIdFromTitle(title)
288 289
    folder = self.getObjectContainer()
    folder._delObject(id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
290 291 292
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

293 294
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_resetPublication')
295
  def manage_resetPublication(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
296 297 298
    """
      reset a publication
    """
299
    pub = self.getPublication(title)
300
    pub.resetAllSubscribers()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
301 302 303
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

304 305
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_resetSubscription')
306
  def manage_resetSubscription(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
307 308 309
    """
      reset a subscription
    """
310
    sub = self.getSubscription(title)
311 312
    sub.resetAllSignatures()
    sub.resetAnchors()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
313 314 315
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

316 317
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_syncSubscription')
318 319 320 321 322 323 324 325
  def manage_syncSubscription(self, title, RESPONSE=None):
    """
      reset a subscription
    """
    self.SubSync(title)
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

326 327
  security.declareProtected(Permissions.AccessContentsInformation,
      'getPublicationList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
328 329 330 331
  def getPublicationList(self):
    """
      Return a list of publications
    """
332 333
    folder = self.getObjectContainer()
    object_list = folder.objectValues()
334 335
    object_list = filter(lambda x: x.id.find('pub')==0,object_list)
    return object_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
336

337 338
  security.declareProtected(Permissions.AccessContentsInformation,
      'getPublication')
339
  def getPublication(self, title):
340
    """
341
      Return the  publications with this id
342
    """
343 344 345
    for p in self.getPublicationList():
      if p.getTitle() == title:
        return p
346
    return None
347

348 349
  security.declareProtected(Permissions.AccessContentsInformation,
      'getObjectContainer')
350 351 352 353 354 355 356 357 358 359 360
  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

361 362
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubscriptionList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
363 364 365 366
  def getSubscriptionList(self):
    """
      Return a list of publications
    """
367 368
    folder = self.getObjectContainer()
    object_list = folder.objectValues()
369 370
    object_list = filter(lambda x: x.id.find('sub')==0,object_list)
    return object_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
371

372
  def getSubscription(self, title):
373 374 375
    """
      Returns the subscription with this id
    """
376 377 378
    for s in self.getSubscriptionList():
      if s.getTitle() == title:
        return s
379 380 381
    return None


382 383
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSynchronizationList')
384
  def getSynchronizationList(self):
385 386
    """
      Returns the list of subscriptions and publications
Sebastien Robin's avatar
Sebastien Robin committed
387

388 389 390
    """
    return self.getSubscriptionList() + self.getPublicationList()

391 392
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubscriberList')
393 394 395 396 397 398 399 400 401 402
  def getSubscriberList(self):
    """
      Returns the list of subscribers and subscriptions
    """
    s_list = []
    s_list += self.getSubscriptionList()
    for publication in self.getPublicationList():
      s_list += publication.getSubscriberList()
    return s_list

403 404
  security.declareProtected(Permissions.AccessContentsInformation,
      'getConflictList')
405
  def getConflictList(self, context=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
406 407 408 409
    """
    Retrieve the list of all conflicts
    Here the list is as follow :
    [conflict_1,conflict2,...] where conflict_1 is like:
410 411
    ['publication',publication_id,object.getPath(),property_id,
    publisher_value,subscriber_value]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
412
    """
413
    path = self.resolveContext(context)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
414 415
    conflict_list = []
    for publication in self.getPublicationList():
Sebastien Robin's avatar
Sebastien Robin committed
416 417 418 419
      for subscriber in publication.getSubscriberList():
        sub_conflict_list = subscriber.getConflictList()
        for conflict in sub_conflict_list:
          #conflict.setDomain('Publication')
420
          conflict.setSubscriber(subscriber)
Sebastien Robin's avatar
Sebastien Robin committed
421
          #conflict.setDomainId(subscriber.getId())
422 423
          if path is None or conflict.getObjectPath() == path:
            conflict_list += [conflict.__of__(subscriber)]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
424 425
    for subscription in self.getSubscriptionList():
      sub_conflict_list = subscription.getConflictList()
426 427
      LOG('SynchronizationTool.getConflictList, sub_conflict_list',0,
          sub_conflict_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
428
      for conflict in sub_conflict_list:
429 430
        if isinstance(conflict,str):
          import pdb; pdb.set_trace()
431
        #conflict.setDomain('Subscription')
432
        conflict.setSubscriber(subscription)
Sebastien Robin's avatar
Sebastien Robin committed
433
        #conflict.setDomainId(subscription.getId())
434 435 436 437 438 439 440 441
        if path is None or conflict.getObjectPath() == path:
          conflict_list += [conflict.__of__(subscription)]
    #if path is not None: # Retrieve only conflicts for a given path
    #  new_list = []
    #  for conflict in conflict_list:
    #    if conflict.getObjectPath() == path:
    #      new_list += [conflict.__of__(self)]
    #  return new_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
442 443
    return conflict_list

444 445
  security.declareProtected(Permissions.AccessContentsInformation,
      'getDocumentConflictList')
446 447 448 449 450 451 452 453
  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)


454 455
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSynchronizationState')
456
  def getSynchronizationState(self, context):
457
    """
458
    context : the context on which we are looking for state
459

460 461 462
    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.
463

464
    This method returns a mapping between subscription and states
Sebastien Robin's avatar
Sebastien Robin committed
465 466 467 468 469

    JPS suggestion:
      path -> object, document, context, etc.
      type -> '/titi/toto' or ('','titi', 'toto') or <Base instance 1562567>
      object = self.resolveContext(context) (method to add)
470
    """
471
    path = self.resolveContext(context)
472 473 474 475 476 477
    conflict_list = self.getConflictList()
    state_list= []
    LOG('getSynchronizationState',0,'path: %s' % str(path))
    for conflict in conflict_list:
      if conflict.getObjectPath() == path:
        LOG('getSynchronizationState',0,'found a conflict: %s' % str(conflict))
478
        state_list += [[conflict.getSubscriber(),self.CONFLICT]]
479
    for domain in self.getSynchronizationList():
480 481 482 483 484 485 486 487 488 489 490 491
      destination = domain.getDestinationPath()
      LOG('getSynchronizationState',0,'destination: %s' % str(destination))
      j_path = '/'.join(path)
      LOG('getSynchronizationState',0,'j_path: %s' % str(j_path))
      if j_path.find(destination)==0:
        o_id = j_path[len(destination)+1:].split('/')[0]
        LOG('getSynchronizationState',0,'o_id: %s' % o_id)
        subscriber_list = []
        if domain.domain_type==self.PUB:
          subscriber_list = domain.getSubscriberList()
        else:
          subscriber_list = [domain]
492
        LOG('getSynchronizationState, subscriber_list:',0,subscriber_list)
493 494 495 496
        for subscriber in subscriber_list:
          signature = subscriber.getSignature(o_id)
          if signature is not None:
            state = signature.getStatus()
497 498
            LOG('getSynchronizationState:',0,'sub.dest :%s, state: %s' % \
                                   (subscriber.getSubscriptionUrl(),str(state)))
499 500 501 502 503 504 505 506
            found = None
            # Make sure there is not already a conflict giving the state
            for state_item in state_list:
              if state_item[0]==subscriber:
                found = 1
            if found is None:
              state_list += [[subscriber,state]]
    return state_list
507

508 509
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applyPublisherValue')
510
  def applyPublisherValue(self, conflict):
Sebastien Robin's avatar
Sebastien Robin committed
511 512 513 514 515
    """
      after a conflict resolution, we have decided
      to keep the local version of an object
    """
    object = self.unrestrictedTraverse(conflict.getObjectPath())
516
    subscriber = conflict.getSubscriber()
Sebastien Robin's avatar
Sebastien Robin committed
517
    # get the signature:
Sebastien Robin's avatar
Sebastien Robin committed
518
    LOG('p_sync.applyPublisherValue, subscriber: ',0,subscriber)
Sebastien Robin's avatar
Sebastien Robin committed
519
    signature = subscriber.getSignature(object.getId()) # XXX may be change for rid
520 521
    copy_path = conflict.getCopyPath()
    LOG('p_sync.applyPublisherValue, copy_path: ',0,copy_path)
Sebastien Robin's avatar
Sebastien Robin committed
522 523
    signature.delConflict(conflict)
    if signature.getConflictList() == []:
524 525 526 527 528 529 530 531 532 533 534 535 536
      if copy_path is not None:
        LOG('p_sync.applyPublisherValue, conflict_list empty on : ',0,signature)
        # Delete the copy of the object if the there is one
        directory = object.aq_parent
        copy_id = copy_path[-1]
        LOG('p_sync.applyPublisherValue, copy_id: ',0,copy_id)
        if hasattr(directory.aq_base, 'hasObject'):
          # optimize the case of a BTree folder
          LOG('p_sync.applyPublisherValue, deleting...: ',0,copy_id)
          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
537 538
      signature.setStatus(self.PUB_CONFLICT_MERGE)

539 540
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applyPublisherDocument')
541 542 543 544 545
  def applyPublisherDocument(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    subscriber = conflict.getSubscriber()
Sebastien Robin's avatar
Sebastien Robin committed
546
    LOG('applyPublisherDocument, subscriber: ',0,subscriber)
547 548
    for c in self.getConflictList(conflict.getObjectPath()):
      if c.getSubscriber() == subscriber:
Sebastien Robin's avatar
Sebastien Robin committed
549
        LOG('applyPublisherDocument, applying on conflict: ',0,conflict)
550 551
        c.applyPublisherValue()

552 553
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getPublisherDocumentPath')
554 555 556 557 558 559 560
  def getPublisherDocumentPath(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    subscriber = conflict.getSubscriber()
    return conflict.getObjectPath()

561 562
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getPublisherDocument')
563 564 565 566 567 568 569 570 571 572
  def getPublisherDocument(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    publisher_object_path = self.getPublisherDocumentPath(conflict)
    LOG('getPublisherDocument publisher_object_path',0,publisher_object_path)
    publisher_object = self.unrestrictedTraverse(publisher_object_path)
    LOG('getPublisherDocument publisher_object',0,publisher_object)
    return publisher_object

573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593

  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)
    publisher_xml = self.getXMLObject(object=publisher_object,xml_mapping\
                                            = subscriber.getXMLMapping())

    directory = publisher_object.aq_parent
    object_id = docid
    if object_id in directory.objectIds():
        directory._delObject(object_id)
594
        # Import the conduit and get it
595
        conduit_name = subscriber.getConduit()
596 597 598
        conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), 
            globals(), locals(), [''])
        conduit = getattr(conduit_module, conduit_name)()
599 600 601 602 603 604 605
        conduit.addNode(xml=publisher_xml,object=directory,object_id=object_id)
        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

606 607 608 609 610 611 612 613 614 615 616 617 618
  def _getCopyId(self, object):
    directory = object.aq_inner.aq_parent
    if directory.getId() != 'portal_repository':    
      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
  
619 620
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubscriberDocumentPath')
621

622 623 624 625
  def getSubscriberDocumentPath(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
626 627
    copy_path = conflict.getCopyPath()
    if copy_path is not None:
628
      return copy_path
629 630 631
    subscriber = conflict.getSubscriber()
    publisher_object_path = conflict.getObjectPath()
    publisher_object = self.unrestrictedTraverse(publisher_object_path)
632 633
    publisher_xml = self.getXMLObject(object=publisher_object, 
        xml_mapping = subscriber.getXMLMapping())
634
    directory = publisher_object.aq_inner.aq_parent
635 636
    object_id = self._getCopyId(publisher_object)    
    # Import the conduit and get it
637
    conduit_name = subscriber.getConduit()
638 639 640 641 642 643 644 645 646
    if conduit_name.startswith('Products'):
      path = conduit_name
      conduit_name = conduit_name.split('.')[-1]
      conduit_module = __import__(path, globals(), locals(), [''])
      conduit = getattr(conduit_module, conduit_name)()
    else:
      conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), 
          globals(), locals(), ['']) 
      conduit = getattr(conduit_module, conduit_name)()
647 648
    conduit.addNode(xml=publisher_xml,object=directory,object_id=object_id)
    subscriber_document = directory._getOb(object_id)
649
    subscriber_document._conflict_resolution = 1
650 651 652
    for c in self.getConflictList(conflict.getObjectPath()):
      if c.getSubscriber() == subscriber:
        c.applySubscriberValue(object=subscriber_document)
653 654 655 656
    copy_path = subscriber_document.getPhysicalPath()
    conflict.setCopyPath(copy_path)
    return copy_path
    
657 658
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubscriberDocument')
659 660 661 662 663 664 665 666
  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

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

678 679
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applySubscriberValue')
680
  def applySubscriberValue(self, conflict,object=None):
Sebastien Robin's avatar
Sebastien Robin committed
681 682 683 684
    """
      after a conflict resolution, we have decided
      to keep the local version of an object
    """
685 686 687 688 689 690 691
    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
692
    subscriber = conflict.getSubscriber()
Sebastien Robin's avatar
Sebastien Robin committed
693 694 695
    # get the signature:
    LOG('p_sync.setRemoteObject, subscriber: ',0,subscriber)
    signature = subscriber.getSignature(object.getId()) # XXX may be change for rid
696
    # Import the conduit and get it
697
    conduit_name = subscriber.getConduit()
698 699
    conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), 
        globals(), locals(), [''])
700
    conduit = getattr(conduit_module, conduit_name)()
Sebastien Robin's avatar
Sebastien Robin committed
701 702
    for xupdate in conflict.getXupdateList():
      conduit.updateNode(xml=xupdate,object=object,force=1)
703
    if solve_conflict:
704
      copy_path = conflict.getCopyPath()
705 706
      signature.delConflict(conflict)
      if signature.getConflictList() == []:
707 708 709 710 711 712 713 714 715 716
        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)
717
        signature.setStatus(self.PUB_CONFLICT_MERGE)
Sebastien Robin's avatar
Sebastien Robin committed
718

719 720 721 722
  security.declareProtected(Permissions.ModifyPortalContent, 
      'managePublisherValue')
  def managePublisherValue(self, subscription_url, property_id, object_path, 
      RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
723 724 725
    """
    Do whatever needed in order to store the local value on
    the remote server
Sebastien Robin's avatar
Sebastien Robin committed
726 727 728

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

747 748 749 750
  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
751 752 753 754
    """
    Do whatever needed in order to store the remote value locally
    and confirmed that the remote box should keep it's value
    """
Sebastien Robin's avatar
Sebastien Robin committed
755
    LOG('manageLocalValue',0,'%s %s %s' % (str(subscription_url),
756
                                           str(property_id),
Sebastien Robin's avatar
Sebastien Robin committed
757 758 759
                                           str(object_path)))
    for conflict in self.getConflictList():
      LOG('manageLocalValue, conflict:',0,conflict)
760 761
      if conflict.getPropertyId() == property_id:
        LOG('manageLocalValue',0,'found the property_id')
Sebastien Robin's avatar
Sebastien Robin committed
762
        if '/'.join(conflict.getObjectPath())==object_path:
763
          if conflict.getSubscriber().getSubscriptionUrl()==subscription_url:
764
            conflict.applySubscriberValue()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
765 766
    if RESPONSE is not None:
      RESPONSE.redirect('manageConflicts')
767
  
768 769
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manageSubscriberDocument')
770 771 772 773 774 775 776 777 778 779
  def manageSubscriberDocument(self, subscription_url, object_path):
    """
    """
    for conflict in self.getConflictList():
      if '/'.join(conflict.getObjectPath())==object_path:
        if conflict.getSubscriber().getSubscriptionUrl()==subscription_url:
          conflict.applySubscriberDocument()
          break
    self.managePublisherDocument(object_path)
  
780 781
  security.declareProtected(Permissions.ModifyPortalContent, 
      'managePublisherDocument')
782 783 784 785 786 787 788 789 790 791 792
  def managePublisherDocument(self, object_path):
    """
    """
    retry = True
    while retry:
      retry = False
      for conflict in self.getConflictList():
        if '/'.join(conflict.getObjectPath())==object_path:
          conflict.applyPublisherDocument()
          retry = True
          break
Jean-Paul Smets's avatar
Jean-Paul Smets committed
793

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

811
  security.declarePublic('sendResponse')
812 813
  def sendResponse(self, to_url=None, from_url=None, sync_id=None,xml=None, 
      domain=None, send=1):
814 815 816 817
    """
    We will look at the url and we will see if we need to send mail, http
    response, or just copy to a file.
    """
818
    LOG('sendResponse, self.getPhysicalPath: ',0,self.getPhysicalPath())
819 820 821
    LOG('sendResponse, to_url: ',0,to_url)
    LOG('sendResponse, from_url: ',0,from_url)
    LOG('sendResponse, sync_id: ',0,sync_id)
Sebastien Robin's avatar
Sebastien Robin committed
822
    LOG('sendResponse, xml: \n',0,xml)
823 824 825 826 827 828 829
    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()
830
        (status,output)=commands.getstatusoutput('gzip /tmp/%s' % filename)
831 832 833
        (status,output)=commands.getstatusoutput('gpg --yes --homedir \
            /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" -se \
            /tmp/%s.gz' % (gpg_key,filename))
834
        LOG('readResponse, gpg output:',0,output)
835
        encrypted = file('/tmp/%s.gz.gpg' % filename,'r')
836 837
        xml = encrypted.read()
        encrypted.close()
838 839 840
        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
841
      if isinstance(to_url, str):
842
        if to_url.find('http://')==0:
843 844 845
          # XXX Make sure this is not a problem
          if domain.domain_type == self.PUB:
            return None
846
          # we will send an http response
Sebastien Robin's avatar
Sebastien Robin committed
847
          domain = aq_base(domain)
848
          LOG('sendResponse, will start sendHttpResponse, xml',0,'')
849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864
          self.activate(activity='RAMQueue').sendHttpResponse(sync_id=sync_id,
                                           to_url=to_url,
                                           xml=xml, domain=domain)
          return None
        elif to_url.find('file://')==0:
          filename = to_url[len('file:/'):]
          stream = file(filename,'w')
          LOG('sendResponse, filename: ',0,filename)
          stream.write(xml)
          stream.close()
          # we have to use local files (unit testing for example
        elif to_url.find('mailto:')==0:
          # we will send an email
          to_address = to_url[len('mailto:'):]
          from_address = from_url[len('mailto:'):]
          self.sendMail(from_address,to_address,sync_id,xml)
865
    return xml
866 867

  security.declarePrivate('sendHttpResponse')
868
  def sendHttpResponse(self, to_url=None, sync_id=None, xml=None, domain=None ):
869
    LOG('sendHttpResponse, self.getPhysicalPath: ',0,self.getPhysicalPath())
870
    LOG('sendHttpResponse, starting with domain:',0,domain)
Sebastien Robin's avatar
Sebastien Robin committed
871
    #LOG('sendHttpResponse, xml:',0,xml)
872 873 874
    if domain is not None:
      if domain.domain_type == self.PUB:
        return xml
875 876 877 878 879 880 881 882 883 884 885 886
    # Retrieve the proxy from os variables
    proxy_url = ''
    if os.environ.has_key('http_proxy'):
      proxy_url = os.environ['http_proxy']
    LOG('sendHttpResponse, proxy_url:',0,proxy_url)
    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)
887 888
    opener = urllib2.build_opener(proxy_handler, proxy_auth_handler, 
        auth_handler, urllib2.HTTPHandler)
889 890
    urllib2.install_opener(opener)
    to_encode = {'text':xml,'sync_id':sync_id}
891
    encoded = urllib.urlencode(to_encode)
892 893
    if to_url.find('readResponse')<0:
      to_url = to_url + '/portal_synchronizations/readResponse'
894
    request = urllib2.Request(url=to_url,data=encoded)
895 896 897 898
    #result = urllib2.urlopen(request).read()
    try:
      result = urllib2.urlopen(request).read()
    except socket.error, msg:
899 900
      self.activate(activity='RAMQueue').sendHttpResponse(to_url=to_url, 
          sync_id=sync_id, xml=xml, domain=domain)
901 902 903
      LOG('sendHttpResponse, socket ERROR:',0,msg)
      return

904
    
905
    LOG('sendHttpResponse, before result, domain:',0,domain)
Sebastien Robin's avatar
Sebastien Robin committed
906
    #LOG('sendHttpResponse, result:',0,result)
907 908
    if domain is not None:
      if domain.domain_type == self.SUB:
909
        gpg_key = domain.getGPGKey()
910
        if result not in (None,''):
911 912
          #if gpg_key not in ('',None):
          #  result = self.sendResponse(domain=domain,xml=result,send=0)
Sebastien Robin's avatar
Sebastien Robin committed
913
          #uf = self.acl_users
914
          #user = UnrestrictedUser('syncml','syncml',['Manager','Member'],'')
Sebastien Robin's avatar
Sebastien Robin committed
915 916
          #user = uf.getUserById('syncml').__of__(uf)
          #newSecurityManager(None, user)
917 918
          #self.activate(activity='RAMQueue').readResponse(sync_id=sync_id,text=result)
          self.readResponse(sync_id=sync_id,text=result)
919 920 921 922 923 924 925 926

  security.declarePublic('sync')
  def sync(self):
    """
    This will try to synchronize every subscription
    """
    # Login as a manager to make sure we can create objects
    uf = self.acl_users
Sebastien Robin's avatar
Sebastien Robin committed
927
    user = UnrestrictedUser('syncml','syncml',['Manager','Member'],'')
928 929 930 931 932 933
    newSecurityManager(None, user)
    message_list = self.portal_activities.getMessageList()
    LOG('sync, message_list:',0,message_list)
    if len(message_list) == 0:
      for subscription in self.getSubscriptionList():
        LOG('sync, subcription:',0,subscription)
934
        self.activate(activity='RAMQueue').SubSync(subscription.getTitle())
935 936 937 938 939 940 941 942

  security.declarePublic('readResponse')
  def readResponse(self, text=None, sync_id=None, to_url=None, from_url=None):
    """
    We will look at the url and we will see if we need to send mail, http
    response, or just copy to a file.
    """
    LOG('readResponse, ',0,'starting')
943
    LOG('readResponse, self.getPhysicalPath: ',0,self.getPhysicalPath())
944
    LOG('readResponse, sync_id: ',0,sync_id)
945 946
    # Login as a manager to make sure we can create objects
    uf = self.acl_users
947
    user = uf.getUserById('syncml').__of__(uf)
Sebastien Robin's avatar
Sebastien Robin committed
948
    user = UnrestrictedUser('syncml','syncml',['Manager','Member'],'')
949
    newSecurityManager(None, user)
950
    status_code = None
951

952
    if text is not None:
953 954 955 956 957
      # XXX We will look everywhere for a publication/subsription with
      # the id sync_id, this is not so good, but there is no way yet
      # to know if we will call a publication or subscription XXX
      gpg_key = ''
      for publication in self.getPublicationList():
958
        if publication.getTitle()==sync_id:
959 960 961
          gpg_key = publication.getGPGKey()
      if gpg_key == '':
        for subscription in self.getSubscriptionList():
962
          if subscription.getTitle()==sync_id:
963 964 965 966
            gpg_key = subscription.getGPGKey()
      # decrypt the message if needed
      if gpg_key not in (None,''):
        filename = str(random.randrange(1,2147483600)) + '.txt'
967
        encrypted = file('/tmp/%s.gz.gpg' % filename,'w')
968 969
        encrypted.write(text)
        encrypted.close()
970 971 972 973
        (status,output)=commands.getstatusoutput('gpg --homedir \
            /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s"  --decrypt \
            /tmp/%s.gz.gpg > /tmp/%s.gz' % (gpg_key, filename, filename))
        LOG('readResponse, gpg output:', 0, output)
974
        (status,output)=commands.getstatusoutput('gunzip /tmp/%s.gz' % filename)
975 976
        decrypted = file('/tmp/%s' % filename,'r')
        text = decrypted.read()
977
        LOG('readResponse, text:', 0, text)
978 979
        decrypted.close()
        commands.getstatusoutput('rm -f /tmp/%s' % filename)
980
        commands.getstatusoutput('rm -f /tmp/%s.gz.gpg' % filename)
981 982
      # Get the target and then find the corresponding publication or
      # Subscription
983
      xml = Parse(text)
Sebastien Robin's avatar
Sebastien Robin committed
984 985
      #XXX this function is not very optimized and should be improved
      url = self.getTarget(xml)
986
      for publication in self.getPublicationList():
987 988
        if publication.getPublicationUrl()==url and \
        publication.getTitle()==sync_id:
989
          result = self.PubSync(sync_id,xml)
990 991
          # Then encrypt the message
          xml = result['xml']
Sebastien Robin's avatar
Sebastien Robin committed
992 993
          #must be commented because this method is alredy called
          #xml = self.sendResponse(xml=xml,domain=publication,send=0)
994
          return xml
995
      
996
      for subscription in self.getSubscriptionList():
Sebastien Robin's avatar
Sebastien Robin committed
997 998
        if subscription.getSubscriptionUrl()==url and \
            subscription.getTitle()==sync_id:
999 1000 1001
              result = self.activate(activity='RAMQueue').SubSync(sync_id, 
                  text)
              #result = self.SubSync(sync_id,xml)
1002 1003

    # we use from only if we have a file 
Sebastien Robin's avatar
Sebastien Robin committed
1004
    elif isinstance(from_url, str):
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
      if from_url.find('file://')==0:
        try:
          filename = from_url[len('file:/'):]
          stream = file(filename,'r')
          xml = stream.read()
          #stream.seek(0)
          #LOG('readResponse',0,'Starting... msg: %s' % str(stream.read()))
        except IOError:
          LOG('readResponse, cannot read file: ',0,filename)
          xml = None
        if xml is not None and len(xml)==0:
          xml = None
        return xml
1018

1019 1020
  security.declareProtected(Permissions.ModifyPortalContent, 
      'getPublicationIdFromTitle')
1021 1022 1023 1024 1025 1026
  def getPublicationIdFromTitle(self, title):
    """
    simply return an id from a title
    """
    return 'pub_' + title

1027 1028
  security.declareProtected(Permissions.ModifyPortalContent, 
      'getPublicationIdFromTitle')
1029 1030 1031 1032 1033 1034
  def getSubscriptionIdFromTitle(self, title):
    """
    simply return an id from a title
    """
    return 'sub_' + title

Sebastien Robin's avatar
Sebastien Robin committed
1035 1036 1037 1038 1039 1040
  security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
  def addNode(self, conduit='ERP5Conduit',**kw):
    """
    """
    # Import the conduit and get it
    from Products.ERP5SyncML import Conduit
1041 1042
    conduit_module = __import__('.'.join([Conduit.__name__, conduit]), 
        globals(), locals(), [''])
Sebastien Robin's avatar
Sebastien Robin committed
1043 1044 1045
    conduit_object = getattr(conduit_module, conduit)()
    return conduit_object.addNode(**kw)

1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063
#  security.declarePrivate('notify_sync')
#  def notify_sync(self, event_type, object, infos):
#    """Notification from the event service.
#
#    # XXX very specific to cps
#
#    Called when an object is added/deleted/modified.
#    Update the date of sync
#    """
#    from Products.CPSCore.utils import _isinstance
#    from Products.CPSCore.ProxyBase import ProxyBase
#
#    if event_type in ('sys_modify_object',
#                      'modify_object'):
#      if not(_isinstance(object, ProxyBase)):
#        repotool = getToolByName(self, 'portal_repository')
#        if repotool.isObjectInRepository(object):
#          object_id = object.getId()
1064 1065


Jean-Paul Smets's avatar
Jean-Paul Smets committed
1066
InitializeClass( SynchronizationTool )