SynchronizationTool.py 40.8 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
from Ft.Xml import Parse
Sebastien Robin's avatar
Sebastien Robin committed
44
from Products.ERP5Type import Permissions
Jean-Paul Smets's avatar
Jean-Paul Smets committed
45 46
from PublicationSynchronization import PublicationSynchronization
from SubscriptionSynchronization import SubscriptionSynchronization
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 socket
55
import os
Jean-Paul Smets's avatar
Jean-Paul Smets committed
56
import string
57 58
import commands
import random
59
from zLOG import LOG
Jean-Paul Smets's avatar
Jean-Paul Smets committed
60

61

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

390 391
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubscriberList')
392 393 394 395 396 397 398 399 400 401
  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

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

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


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

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

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

    JPS suggestion:
      path -> object, document, context, etc.
      type -> '/titi/toto' or ('','titi', 'toto') or <Base instance 1562567>
      object = self.resolveContext(context) (method to add)
469
    """
470
    path = self.resolveContext(context)
471 472 473 474 475 476
    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))
477
        state_list += [[conflict.getSubscriber(),self.CONFLICT]]
478
    for domain in self.getSynchronizationList():
479 480 481 482 483 484 485 486 487 488 489 490
      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]
491
        LOG('getSynchronizationState, subscriber_list:',0,subscriber_list)
492 493 494 495
        for subscriber in subscriber_list:
          signature = subscriber.getSignature(o_id)
          if signature is not None:
            state = signature.getStatus()
496 497
            LOG('getSynchronizationState:',0,'sub.dest :%s, state: %s' % \
                                   (subscriber.getSubscriptionUrl(),str(state)))
498 499 500 501 502 503 504 505
            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
506

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

538 539
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applyPublisherDocument')
540 541 542 543 544
  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
545
    LOG('applyPublisherDocument, subscriber: ',0,subscriber)
546 547
    for c in self.getConflictList(conflict.getObjectPath()):
      if c.getSubscriber() == subscriber:
Sebastien Robin's avatar
Sebastien Robin committed
548
        LOG('applyPublisherDocument, applying on conflict: ',0,conflict)
549 550
        c.applyPublisherValue()

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

560 561
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getPublisherDocument')
562 563 564 565 566 567 568 569 570 571
  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

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

  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)
593
        # Import the conduit and get it
594
        conduit_name = subscriber.getConduit()
595 596 597
        conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), 
            globals(), locals(), [''])
        conduit = getattr(conduit_module, conduit_name)()
598 599 600 601 602 603 604
        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

605 606 607 608 609 610 611 612 613 614 615 616 617
  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
  
618 619
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubscriberDocumentPath')
620

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

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

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

718 719 720 721
  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
722 723 724
    """
    Do whatever needed in order to store the local value on
    the remote server
Sebastien Robin's avatar
Sebastien Robin committed
725 726 727

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

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

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

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

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

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

  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
926
    user = UnrestrictedUser('syncml','syncml',['Manager','Member'],'')
927 928 929 930 931 932
    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)
933
        self.activate(activity='RAMQueue').SubSync(subscription.getTitle())
934 935 936 937 938 939 940 941

  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')
942
    LOG('readResponse, self.getPhysicalPath: ',0,self.getPhysicalPath())
943
    LOG('readResponse, sync_id: ',0,sync_id)
944 945
    # Login as a manager to make sure we can create objects
    uf = self.acl_users
946
    user = uf.getUserById('syncml').__of__(uf)
Sebastien Robin's avatar
Sebastien Robin committed
947
    user = UnrestrictedUser('syncml','syncml',['Manager','Member'],'')
948
    newSecurityManager(None, user)
949
    status_code = None
950

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

    # we use from only if we have a file 
Sebastien Robin's avatar
Sebastien Robin committed
1003
    elif isinstance(from_url, str):
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
      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
1017

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

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

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

1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062
#  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()
1063 1064


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