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

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

from OFS.SimpleItem import SimpleItem
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32
from Products.ERP5Type.Core.Folder import Folder
33
from Products.ERP5Type.Base import Base
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34 35 36 37 38
from Products.CMFCore.utils import UniqueObject
from Globals import InitializeClass, DTMLFile, PersistentMapping, Persistent
from AccessControl import ClassSecurityInfo, getSecurityManager
from Products.CMFCore import CMFCorePermissions
from Products.ERP5SyncML import _dtmldir
39
from Products.ERP5SyncML import Conduit
Jean-Paul Smets's avatar
Jean-Paul Smets committed
40
from Publication import Publication,Subscriber
41
from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2
Jean-Paul Smets's avatar
Jean-Paul Smets committed
42
from Subscription import Subscription,Signature
43 44
from XMLSyncUtils import Parse
#from Ft.Xml import Parse
Sebastien Robin's avatar
Sebastien Robin committed
45
from Products.ERP5Type import Permissions
Jean-Paul Smets's avatar
Jean-Paul Smets committed
46 47
from PublicationSynchronization import PublicationSynchronization
from SubscriptionSynchronization import SubscriptionSynchronization
48
from SyncCode import SyncCode
49
from Products.CMFCore.utils import getToolByName
50
from AccessControl.SecurityManagement import newSecurityManager
51
from AccessControl.SecurityManagement import noSecurityManager
52
from AccessControl.User import UnrestrictedUser
Sebastien Robin's avatar
Sebastien Robin committed
53
from Acquisition import aq_base
54
import urllib
55
import urllib2
56
import httplib
57
import socket
58
import os
Jean-Paul Smets's avatar
Jean-Paul Smets committed
59
import string
60 61
import commands
import random
62
from DateTime import DateTime
63
from zLOG import LOG
Jean-Paul Smets's avatar
Jean-Paul Smets committed
64

65 66 67 68 69 70 71 72 73 74 75
class TimeoutHTTPConnection(httplib.HTTPConnection):
  """
  Custom Classes to set timeOut on handle sockets
  """
  def connect(self):
    httplib.HTTPConnection.connect(self)
    self.sock.settimeout(3600)

class TimeoutHTTPHandler(urllib2.HTTPHandler):
  def http_open(self, req):
    return self.do_open(TimeoutHTTPConnection, req)
76

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

78

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

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


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

92 93 94 95
  # On the server, this is use to keep track of the temporary
  # copies.
  objectsToRemove = [] 
  
Jean-Paul Smets's avatar
Jean-Paul Smets committed
96 97 98 99 100 101 102 103 104 105 106 107 108
  security = ClassSecurityInfo()

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

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

109 110 111 112
  # Multiple inheritance inconsistency caused by Base must be circumvented
  def __init__( self, *args, **kwargs ):
    Folder.__init__(self, self.id, **kwargs)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

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

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

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

  security.declareProtected( CMFCorePermissions.ManagePortal
142 143
               , 'manage_addPublicationForm' )
  manage_addPublicationForm = DTMLFile( 'dtml/manage_addPublication', globals() )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
144 145

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

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

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

  security.declareProtected( CMFCorePermissions.ManagePortal
               , 'editProperties' )
  def editProperties( self
           , publisher=None
           , REQUEST=None
           ):
    """
      Form handler for "tool-wide" properties (including list of
      metadata elements).
    """
    if publisher is not None:
      self.publisher = publisher

    if REQUEST is not None:
      REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
                    + '/propertiesForm'
                    + '?manage_tabs_message=Tool+updated.'
                    )

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

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

241
  security.declareProtected(Permissions.ModifyPortalContent,
242
      'manage_editPublication')
243 244 245 246 247
  def manage_editPublication(self, title, publication_url,
                            destination_path, source_uri, query, xml_mapping,
                            conduit, gpg_key, synchronization_id_generator,
                            gid_generator,  media_type=None, auth_required=0,
                            authentication_format='', authentication_type='',
248 249 250
                            RESPONSE=None, activity_enabled=False,
                            sync_content_type='application/vnd.syncml+xml',
                            synchronize_with_erp5_sites=False):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
251 252 253
    """
      modify a publication
    """
254
    pub = self.getPublication(title)
255
    pub.setTitle(title)
Nicolas Delaby's avatar
Nicolas Delaby committed
256
    pub.setActivityEnabled(activity_enabled)
257 258
    pub.setPublicationUrl(publication_url)
    pub.setDestinationPath(destination_path)
259
    pub.setSourceURI(source_uri)
260
    pub.setQuery(query)
261
    pub.setConduit(conduit)
262 263
    pub.setXMLMapping(xml_mapping)
    pub.setGPGKey(gpg_key)
264
    pub.setSynchronizationIdGenerator(synchronization_id_generator)
265
    pub.setGidGenerator(gid_generator)
266
    pub.setMediaType(media_type)
267 268 269
    pub.setAuthentication(auth_required)
    pub.setAuthenticationFormat(authentication_format)
    pub.setAuthenticationType(authentication_type)
270 271
    pub.setSyncContentType(sync_content_type)
    pub.setSynchronizeWithERP5Sites(synchronize_with_erp5_sites)
272

Jean-Paul Smets's avatar
Jean-Paul Smets committed
273 274 275
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
308 309 310
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

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

323 324
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_deleteSubscription')
325
  def manage_deleteSubscription(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
326 327 328
    """
      delete a subscription
    """
329
    id = self.getSubscriptionIdFromTitle(title)
330 331
    folder = self.getObjectContainer()
    folder._delObject(id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
332 333 334
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

335 336
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_resetPublication')
337
  def manage_resetPublication(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
338 339 340
    """
      reset a publication
    """
341
    pub = self.getPublication(title)
342
    pub.resetAllSubscribers()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
343 344 345
    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')

346 347
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_resetSubscription')
348
  def manage_resetSubscription(self, title, RESPONSE=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
349 350 351
    """
      reset a subscription
    """
352
    sub = self.getSubscription(title)
353 354
    sub.resetAllSignatures()
    sub.resetAnchors()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
355 356 357
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

358 359
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manage_syncSubscription')
360 361 362 363
  def manage_syncSubscription(self, title, RESPONSE=None):
    """
      reset a subscription
    """
364
    self.SubSync(self.getSubscription(title).getPath())
365 366 367
    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')

368 369
  security.declareProtected(Permissions.AccessContentsInformation,
      'getPublicationList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
370 371 372 373
  def getPublicationList(self):
    """
      Return a list of publications
    """
374 375
    folder = self.getObjectContainer()
    object_list = folder.objectValues()
376 377
    object_list = filter(lambda x: x.id.find('pub')==0,object_list)
    return object_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
378

379 380
  security.declareProtected(Permissions.AccessContentsInformation,
      'getPublication')
381
  def getPublication(self, title):
382
    """
383
      Return the  publications with this id
384
    """
385 386 387
    for p in self.getPublicationList():
      if p.getTitle() == title:
        return p
388
    return None
389

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

403 404
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubscriptionList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
405 406 407 408
  def getSubscriptionList(self):
    """
      Return a list of publications
    """
409 410
    folder = self.getObjectContainer()
    object_list = folder.objectValues()
411 412
    object_list = filter(lambda x: x.id.find('sub')==0,object_list)
    return object_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
413

414
  def getSubscription(self, title):
415
    """
416
      Returns the subscription with this title
417
    """
418 419 420
    for s in self.getSubscriptionList():
      if s.getTitle() == title:
        return s
421 422 423
    return None


424 425
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSynchronizationList')
426
  def getSynchronizationList(self):
427 428
    """
      Returns the list of subscriptions and publications
Sebastien Robin's avatar
Sebastien Robin committed
429

430 431 432
    """
    return self.getSubscriptionList() + self.getPublicationList()

433 434
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubscriberList')
435 436 437 438 439 440 441 442 443 444
  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

445 446
  security.declareProtected(Permissions.AccessContentsInformation,
      'getConflictList')
447
  def getConflictList(self, context=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
448 449 450 451
    """
    Retrieve the list of all conflicts
    Here the list is as follow :
    [conflict_1,conflict2,...] where conflict_1 is like:
452 453
    ['publication',publication_id,object.getPath(),property_id,
    publisher_value,subscriber_value]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
454
    """
455
    path = self.resolveContext(context)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
456 457
    conflict_list = []
    for publication in self.getPublicationList():
Sebastien Robin's avatar
Sebastien Robin committed
458 459 460 461
      for subscriber in publication.getSubscriberList():
        sub_conflict_list = subscriber.getConflictList()
        for conflict in sub_conflict_list:
          #conflict.setDomain('Publication')
462
          conflict.setSubscriber(subscriber)
Sebastien Robin's avatar
Sebastien Robin committed
463
          #conflict.setDomainId(subscriber.getId())
464 465
          if path is None or conflict.getObjectPath() == path:
            conflict_list += [conflict.__of__(subscriber)]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
466 467
    for subscription in self.getSubscriptionList():
      sub_conflict_list = subscription.getConflictList()
Nicolas Delaby's avatar
Nicolas Delaby committed
468
      #LOG('SynchronizationTool.getConflictList, sub_conflict_list',0,
Nicolas Delaby's avatar
Nicolas Delaby committed
469
          #sub_conflict_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
470
      for conflict in sub_conflict_list:
471
        #conflict.setDomain('Subscription')
472
        conflict.setSubscriber(subscription)
Sebastien Robin's avatar
Sebastien Robin committed
473
        #conflict.setDomainId(subscription.getId())
474 475 476 477 478 479 480 481
        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
482 483
    return conflict_list

484 485
  security.declareProtected(Permissions.AccessContentsInformation,
      'getDocumentConflictList')
486 487 488 489 490 491 492 493
  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)


494 495
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSynchronizationState')
496
  def getSynchronizationState(self, context):
497
    """
498
    context : the context on which we are looking for state
499

500 501 502
    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.
503

504
    This method returns a mapping between subscription and states
Sebastien Robin's avatar
Sebastien Robin committed
505 506 507 508 509

    JPS suggestion:
      path -> object, document, context, etc.
      type -> '/titi/toto' or ('','titi', 'toto') or <Base instance 1562567>
      object = self.resolveContext(context) (method to add)
510
    """
511
    path = self.resolveContext(context)
512 513
    conflict_list = self.getConflictList()
    state_list= []
Nicolas Delaby's avatar
Nicolas Delaby committed
514
    #LOG('getSynchronizationState',0,'path: %s' % str(path))
515 516
    for conflict in conflict_list:
      if conflict.getObjectPath() == path:
Nicolas Delaby's avatar
Nicolas Delaby committed
517
        #LOG('getSynchronizationState',0,'found a conflict: %s' % str(conflict))
518
        state_list += [[conflict.getSubscriber(),self.CONFLICT]]
519
    for domain in self.getSynchronizationList():
520
      destination = domain.getDestinationPath()
Nicolas Delaby's avatar
Nicolas Delaby committed
521
      #LOG('getSynchronizationState',0,'destination: %s' % str(destination))
522
      j_path = '/'.join(path)
Nicolas Delaby's avatar
Nicolas Delaby committed
523
      #LOG('getSynchronizationState',0,'j_path: %s' % str(j_path))
524 525
      if j_path.find(destination)==0:
        o_id = j_path[len(destination)+1:].split('/')[0]
Nicolas Delaby's avatar
Nicolas Delaby committed
526
        #LOG('getSynchronizationState',0,'o_id: %s' % o_id)
527 528 529 530 531
        subscriber_list = []
        if domain.domain_type==self.PUB:
          subscriber_list = domain.getSubscriberList()
        else:
          subscriber_list = [domain]
Nicolas Delaby's avatar
Nicolas Delaby committed
532
        #LOG('getSynchronizationState, subscriber_list:',0,subscriber_list)
533
        for subscriber in subscriber_list:
534
          signature = subscriber.getSignatureFromObjectId(o_id)
535
          #XXX check if signature could be not None ...
536 537
          if signature is not None:
            state = signature.getStatus()
Nicolas Delaby's avatar
Nicolas Delaby committed
538
            #LOG('getSynchronizationState:',0,'sub.dest :%s, state: %s' % \
Nicolas Delaby's avatar
Nicolas Delaby committed
539
                                   #(subscriber.getSubscriptionUrl(),str(state)))
540 541 542 543 544 545 546 547
            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
548

549 550
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applyPublisherValue')
551
  def applyPublisherValue(self, conflict):
Sebastien Robin's avatar
Sebastien Robin committed
552 553 554 555 556
    """
      after a conflict resolution, we have decided
      to keep the local version of an object
    """
    object = self.unrestrictedTraverse(conflict.getObjectPath())
557
    subscriber = conflict.getSubscriber()
Sebastien Robin's avatar
Sebastien Robin committed
558
    # get the signature:
Nicolas Delaby's avatar
Nicolas Delaby committed
559
    #LOG('p_sync.applyPublisherValue, subscriber: ',0,subscriber)
560
    signature = subscriber.getSignatureFromObjectId(object.getId()) # XXX may be change for rid
561
    copy_path = conflict.getCopyPath()
Nicolas Delaby's avatar
Nicolas Delaby committed
562
    #LOG('p_sync.applyPublisherValue, copy_path: ',0,copy_path)
Sebastien Robin's avatar
Sebastien Robin committed
563 564
    signature.delConflict(conflict)
    if signature.getConflictList() == []:
565
      if copy_path is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
566
        #LOG('p_sync.applyPublisherValue, conflict_list empty on : ',0,signature)
567 568 569
        # Delete the copy of the object if the there is one
        directory = object.aq_parent
        copy_id = copy_path[-1]
Nicolas Delaby's avatar
Nicolas Delaby committed
570
        #LOG('p_sync.applyPublisherValue, copy_id: ',0,copy_id)
571 572
        if hasattr(directory.aq_base, 'hasObject'):
          # optimize the case of a BTree folder
Nicolas Delaby's avatar
Nicolas Delaby committed
573
          #LOG('p_sync.applyPublisherValue, deleting...: ',0,copy_id)
574 575 576 577
          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
578 579
      signature.setStatus(self.PUB_CONFLICT_MERGE)

580 581
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applyPublisherDocument')
582 583 584 585 586
  def applyPublisherDocument(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    subscriber = conflict.getSubscriber()
Nicolas Delaby's avatar
Nicolas Delaby committed
587
    #LOG('applyPublisherDocument, subscriber: ',0,subscriber)
588 589
    for c in self.getConflictList(conflict.getObjectPath()):
      if c.getSubscriber() == subscriber:
Nicolas Delaby's avatar
Nicolas Delaby committed
590
        #LOG('applyPublisherDocument, applying on conflict: ',0,conflict)
591 592
        c.applyPublisherValue()

593 594
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getPublisherDocumentPath')
595 596 597 598 599 600 601
  def getPublisherDocumentPath(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    subscriber = conflict.getSubscriber()
    return conflict.getObjectPath()

602 603
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getPublisherDocument')
604 605 606 607 608
  def getPublisherDocument(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
    publisher_object_path = self.getPublisherDocumentPath(conflict)
Nicolas Delaby's avatar
Nicolas Delaby committed
609
    #LOG('getPublisherDocument publisher_object_path',0,publisher_object_path)
610
    publisher_object = self.unrestrictedTraverse(publisher_object_path)
Nicolas Delaby's avatar
Nicolas Delaby committed
611
    #LOG('getPublisherDocument publisher_object',0,publisher_object)
612 613
    return publisher_object

614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634

  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)
635
        # Import the conduit and get it
636
        conduit_name = subscriber.getConduit()
637 638 639
        conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), 
            globals(), locals(), [''])
        conduit = getattr(conduit_module, conduit_name)()
640 641 642 643 644 645 646
        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

647 648 649 650 651 652 653 654 655 656 657 658 659
  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
  
660 661
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubscriberDocumentPath')
662

663 664 665 666
  def getSubscriberDocumentPath(self, conflict):
    """
    apply the publisher value for all conflict of the given document
    """
667 668
    copy_path = conflict.getCopyPath()
    if copy_path is not None:
669
      return copy_path
670 671 672
    subscriber = conflict.getSubscriber()
    publisher_object_path = conflict.getObjectPath()
    publisher_object = self.unrestrictedTraverse(publisher_object_path)
Nicolas Delaby's avatar
Nicolas Delaby committed
673
    publisher_xml = subscriber.getXMLFromObject(publisher_object)
674
    directory = publisher_object.aq_inner.aq_parent
675 676
    object_id = self._getCopyId(publisher_object)    
    # Import the conduit and get it
677
    conduit_name = subscriber.getConduit()
678 679 680 681 682 683 684 685 686
    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)()
687 688
    conduit.addNode(xml=publisher_xml,object=directory,object_id=object_id)
    subscriber_document = directory._getOb(object_id)
689
    subscriber_document._conflict_resolution = 1
690 691 692
    for c in self.getConflictList(conflict.getObjectPath()):
      if c.getSubscriber() == subscriber:
        c.applySubscriberValue(object=subscriber_document)
693 694 695 696
    copy_path = subscriber_document.getPhysicalPath()
    conflict.setCopyPath(copy_path)
    return copy_path
    
697 698
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubscriberDocument')
699 700 701 702 703 704 705 706
  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

707 708
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applySubscriberDocument')
709 710 711 712 713 714 715 716 717
  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()

718 719
  security.declareProtected(Permissions.ModifyPortalContent, 
      'applySubscriberValue')
720
  def applySubscriberValue(self, conflict,object=None):
Sebastien Robin's avatar
Sebastien Robin committed
721 722 723 724
    """
      after a conflict resolution, we have decided
      to keep the local version of an object
    """
725 726 727 728 729 730 731
    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
732
    subscriber = conflict.getSubscriber()
Sebastien Robin's avatar
Sebastien Robin committed
733
    # get the signature:
Nicolas Delaby's avatar
Nicolas Delaby committed
734
    #LOG('p_sync.setRemoteObject, subscriber: ',0,subscriber)
735
    signature = subscriber.getSignatureFromObjectId(object.getId()) # XXX may be change for rid
736
    # Import the conduit and get it
737
    conduit_name = subscriber.getConduit()
738 739
    conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), 
        globals(), locals(), [''])
740
    conduit = getattr(conduit_module, conduit_name)()
Sebastien Robin's avatar
Sebastien Robin committed
741 742
    for xupdate in conflict.getXupdateList():
      conduit.updateNode(xml=xupdate,object=object,force=1)
743
    if solve_conflict:
744
      copy_path = conflict.getCopyPath()
745 746
      signature.delConflict(conflict)
      if signature.getConflictList() == []:
747 748 749 750 751 752 753 754 755 756
        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)
757
        signature.setStatus(self.PUB_CONFLICT_MERGE)
Sebastien Robin's avatar
Sebastien Robin committed
758

759 760 761 762
  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
763 764 765
    """
    Do whatever needed in order to store the local value on
    the remote server
Sebastien Robin's avatar
Sebastien Robin committed
766 767 768

    Suggestion (API)
      add method to view document with applied xupdate
769 770
      of a given subscriber XX 
      (ex. viewSubscriberDocument?path=ddd&subscriber_id=dddd)
Sebastien Robin's avatar
Sebastien Robin committed
771
      Version=Version CPS
Jean-Paul Smets's avatar
Jean-Paul Smets committed
772 773
    """
    # Retrieve the conflict object
Nicolas Delaby's avatar
Nicolas Delaby committed
774 775 776
    #LOG('manageLocalValue',0,'%s %s %s' % (str(subscription_url),
    #                                       str(property_id),
    #                                       str(object_path)))
Sebastien Robin's avatar
Sebastien Robin committed
777
    for conflict in self.getConflictList():
Nicolas Delaby's avatar
Nicolas Delaby committed
778
      #LOG('manageLocalValue, conflict:',0,conflict)
779
      if conflict.getPropertyId() == property_id:
Nicolas Delaby's avatar
Nicolas Delaby committed
780
        #LOG('manageLocalValue',0,'found the property_id')
Sebastien Robin's avatar
Sebastien Robin committed
781
        if '/'.join(conflict.getObjectPath())==object_path:
782
          if conflict.getSubscriber().getSubscriptionUrl()==subscription_url:
783
            conflict.applyPublisherValue()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
784 785 786
    if RESPONSE is not None:
      RESPONSE.redirect('manageConflicts')

787 788 789 790
  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
791 792 793 794
    """
    Do whatever needed in order to store the remote value locally
    and confirmed that the remote box should keep it's value
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
795
    #LOG('manageLocalValue',0,'%s %s %s' % (str(subscription_url),
Nicolas Delaby's avatar
Nicolas Delaby committed
796 797
                         #                  str(property_id),
                         #                  str(object_path)))
Sebastien Robin's avatar
Sebastien Robin committed
798
    for conflict in self.getConflictList():
Nicolas Delaby's avatar
Nicolas Delaby committed
799
      #LOG('manageLocalValue, conflict:',0,conflict)
800
      if conflict.getPropertyId() == property_id:
Nicolas Delaby's avatar
Nicolas Delaby committed
801
        #LOG('manageLocalValue',0,'found the property_id')
Sebastien Robin's avatar
Sebastien Robin committed
802
        if '/'.join(conflict.getObjectPath())==object_path:
803
          if conflict.getSubscriber().getSubscriptionUrl()==subscription_url:
804
            conflict.applySubscriberValue()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
805 806
    if RESPONSE is not None:
      RESPONSE.redirect('manageConflicts')
807
  
808 809
  security.declareProtected(Permissions.ModifyPortalContent, 
      'manageSubscriberDocument')
810 811 812 813 814 815 816 817 818 819
  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)
  
820 821
  security.declareProtected(Permissions.ModifyPortalContent, 
      'managePublisherDocument')
822 823 824 825 826 827 828 829 830 831 832
  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
833

834 835 836 837 838 839 840 841 842 843
  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
844
    elif isinstance(context, tuple):
845
      return context
Sebastien Robin's avatar
Sebastien Robin committed
846
    elif isinstance(context, tuple):
847 848 849 850
      return tuple(context.split('/'))
    else:
      return context.getPhysicalPath()

851
  security.declarePublic('sendResponse')
852
  def sendResponse(self, to_url=None, from_url=None, sync_id=None,xml=None, 
853
      domain=None, send=1, content_type='application/vnd.syncml+xml'):
854 855 856 857
    """
    We will look at the url and we will see if we need to send mail, http
    response, or just copy to a file.
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
858 859 860 861
    #LOG('sendResponse, self.getPhysicalPath: ',0,self.getPhysicalPath())
    #LOG('sendResponse, to_url: ',0,to_url)
    #LOG('sendResponse, from_url: ',0,from_url)
    #LOG('sendResponse, sync_id: ',0,sync_id)
Nicolas Delaby's avatar
Nicolas Delaby committed
862
    #LOG('sendResponse, xml: \n',0,xml)
863 864 865 866 867 868

    if content_type == self.CONTENT_TYPE['SYNCML_WBXML']:
      xml = self.xml2wbxml(xml)
      #LOG('sendHttpResponse, xml after wbxml: \n',0,self.hexdump(xml))


Nicolas Delaby's avatar
Nicolas Delaby committed
869 870
    if isinstance(xml, unicode):
      xml = xml.encode('utf-8')
871 872 873 874 875 876 877
    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()
878
        (status,output)=commands.getstatusoutput('gzip /tmp/%s' % filename)
879 880 881
        (status,output)=commands.getstatusoutput('gpg --yes --homedir \
            /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" -se \
            /tmp/%s.gz' % (gpg_key,filename))
Nicolas Delaby's avatar
Nicolas Delaby committed
882
        #LOG('readResponse, gpg output:',0,output)
883
        encrypted = file('/tmp/%s.gz.gpg' % filename,'r')
884 885
        xml = encrypted.read()
        encrypted.close()
886 887 888
        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
889
      if isinstance(to_url, str):
890
        if to_url.find('http://')==0:
Sebastien Robin's avatar
Sebastien Robin committed
891
          domain = aq_base(domain)
892 893 894 895 896 897 898 899
          #LOG('domain.domain_type',0,domain.domain_type)
          #LOG("getattr(domain, 'getActivityEnabled', None)",0,getattr(domain, 'getActivityEnabled', None))
          #LOG("domain.getActivityEnabled()",0,domain.getActivityEnabled())
          if domain.domain_type == self.PUB and not domain.getActivityEnabled():
            # not use activity
            # XXX Make sure this is not a problem
            return None
          #use activities to send send an http response
Nicolas Delaby's avatar
Nicolas Delaby committed
900
          #LOG('sendResponse, will start sendHttpResponse, xml',0,'')
901 902
          self.activate(activity='RAMQueue').sendHttpResponse(sync_id=sync_id,
                                           to_url=to_url,
903
                                           xml=xml, 
904 905
                                           domain_path=domain.getPath(),
                                           content_type=content_type)
906 907 908
        elif to_url.find('file://')==0:
          filename = to_url[len('file:/'):]
          stream = file(filename,'w')
Nicolas Delaby's avatar
Nicolas Delaby committed
909
          #LOG('sendResponse, filename: ',0,filename)
910 911 912 913 914 915 916 917
          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)
918
    return xml
919 920

  security.declarePrivate('sendHttpResponse')
921
  def sendHttpResponse(self, to_url=None, sync_id=None, xml=None, 
922
      domain_path=None, content_type='application/vnd.syncml+xml'):
923
    domain = self.unrestrictedTraverse(domain_path)
Nicolas Delaby's avatar
Nicolas Delaby committed
924 925
    #LOG('sendHttpResponse, self.getPhysicalPath: ',0,self.getPhysicalPath())
    #LOG('sendHttpResponse, starting with domain:',0,domain)
926

Sebastien Robin's avatar
Sebastien Robin committed
927
    #LOG('sendHttpResponse, xml:',0,xml)
928
    if domain is not None:
929 930
      if domain.domain_type == self.PUB and not domain.getActivityEnabled():
            return xml
931 932 933 934
    # Retrieve the proxy from os variables
    proxy_url = ''
    if os.environ.has_key('http_proxy'):
      proxy_url = os.environ['http_proxy']
Nicolas Delaby's avatar
Nicolas Delaby committed
935
    #LOG('sendHttpResponse, proxy_url:',0,proxy_url)
936 937 938 939 940 941 942
    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)
943 944
    opener = urllib2.build_opener(proxy_handler, proxy_auth_handler,
        auth_handler, TimeoutHTTPHandler)
945
    urllib2.install_opener(opener)
946 947
    to_encode = {}
    head = '<?xml version="1.0" encoding="UTF-8"?>'
948 949 950 951 952 953

    if content_type == self.CONTENT_TYPE['SYNCML_WBXML']:
      #because xml2wbxml add the head to the xml
      to_encode['text'] = xml
    else:
      to_encode['text'] = head + xml
954
    to_encode['sync_id'] = sync_id
955 956
    headers = {'User-Agent':'ERP5SyncML', 'Content-Type':content_type}

957
    #XXX bad hack for synchronization with erp5
958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978
    # because at this time, when we call the readResponse method, we must
    # encode the data with urlencode if we want the readResponse method to 
    # receive the data's in parameters.
    # All this should be improved to not use urlencode in all cases.
    # to do this, perhaps use SOAP :
    #  - http://en.wikipedia.org/wiki/SOAP
    #  - http://www.contentmanagementsoftware.info/zope/SOAPSupport
    #  - http://svn.zope.org/soap/trunk/

    if domain.getSynchronizeWithERP5Sites():
      LOG('Synchronization with another ERP5 instance ...',0,'')
      if to_url.find('readResponse')<0:
        to_url = to_url + '/portal_synchronizations/readResponse'
      encoded = urllib.urlencode(to_encode)
      data=encoded
      request = urllib2.Request(url=to_url, data=data)
    else:
    #XXX only to synchronize with other server than erp5 (must be improved):
      data=head+xml
      request = urllib2.Request(to_url, data, headers)

979
    try:
980 981
      url_file = urllib2.urlopen(request)
      result = url_file.read()
982
    except socket.error, msg:
983
      self.activate(activity='RAMQueue').sendHttpResponse(to_url=to_url, 
984 985
          sync_id=sync_id, xml=xml, domain_path=domain.getPath(), 
          content_type=content_type)
986
      LOG('sendHttpResponse, socket ERROR:',0,msg)
987
      #LOG('sendHttpResponse, url,data',0,(url, data))
988
      return
989 990
    except urllib2.URLError, msg:
      LOG("sendHttpResponse, can't open url %s :" % to_url, 0, msg)
991
      LOG('sendHttpResponse, to_url,data',0,(to_url, data))
992 993
      return

994

Nicolas Delaby's avatar
Nicolas Delaby committed
995
    #LOG('sendHttpResponse, before result, domain:',0,domain)
996
    if domain is not None:
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
      if domain.domain_type == self.SUB and not domain.getActivityEnabled():
            #if we don't use activity :
            gpg_key = domain.getGPGKey()
            if result not in (None,''):
              #if gpg_key not in ('',None):
              #  result = self.sendResponse(domain=domain,xml=result,send=0)
              #uf = self.acl_users
              #user = UnrestrictedUser('syncml','syncml',['Manager','Member'],'')
              #user = uf.getUserById('syncml').__of__(uf)
              #newSecurityManager(None, user)
              #self.activate(activity='RAMQueue').readResponse(sync_id=sync_id,text=result)
1008
              
1009
              self.readResponse(sync_id=sync_id,text=result)
1010
    return result
1011 1012 1013 1014 1015 1016 1017 1018

  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
1019
    user = UnrestrictedUser('syncml','syncml',['Manager','Member'],'')
1020 1021
    newSecurityManager(None, user)
    message_list = self.portal_activities.getMessageList()
Nicolas Delaby's avatar
Nicolas Delaby committed
1022
    #LOG('sync, message_list:',0,message_list)
1023 1024
    if len(message_list) == 0:
      for subscription in self.getSubscriptionList():
1025 1026
        #LOG('sync, type(subcription):',0,type(subscription))
        self.activate(activity='RAMQueue').SubSync(subscription.getPath())
1027 1028 1029 1030 1031 1032 1033

  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.
    """
1034
    #LOG('readResponse, text :', 0, text)
1035 1036
    #LOG('readResponse, text :', 0, self.hexdump(text))

1037 1038
    # Login as a manager to make sure we can create objects
    uf = self.acl_users
1039
    user = uf.getUserById('syncml').__of__(uf)
Sebastien Robin's avatar
Sebastien Robin committed
1040
    user = UnrestrictedUser('syncml','syncml',['Manager','Member'],'')
1041
    newSecurityManager(None, user)
1042
    status_code = None
1043

1044
    if text is not None:
1045 1046 1047 1048 1049
      # 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():
1050
        if publication.getTitle()==sync_id:
1051
          gpg_key = publication.getGPGKey()
1052
          domain = publication
1053 1054
      if gpg_key == '':
        for subscription in self.getSubscriptionList():
1055
          if subscription.getTitle()==sync_id:
1056
            gpg_key = subscription.getGPGKey()
1057
            domain = subscription
1058 1059 1060
      # decrypt the message if needed
      if gpg_key not in (None,''):
        filename = str(random.randrange(1,2147483600)) + '.txt'
1061
        encrypted = file('/tmp/%s.gz.gpg' % filename,'w')
1062 1063
        encrypted.write(text)
        encrypted.close()
1064 1065 1066
        (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))
Nicolas Delaby's avatar
Nicolas Delaby committed
1067
        #LOG('readResponse, gpg output:', 0, output)
1068
        (status,output)=commands.getstatusoutput('gunzip /tmp/%s.gz' % filename)
1069 1070
        decrypted = file('/tmp/%s' % filename,'r')
        text = decrypted.read()
Nicolas Delaby's avatar
Nicolas Delaby committed
1071
        #LOG('readResponse, text:', 0, text)
1072 1073
        decrypted.close()
        commands.getstatusoutput('rm -f /tmp/%s' % filename)
1074
        commands.getstatusoutput('rm -f /tmp/%s.gz.gpg' % filename)
1075 1076
      # Get the target and then find the corresponding publication or
      # Subscription
1077
      #LOG('type(text) : ',0,type(text))
1078 1079 1080
      if domain.getSyncContentType() == self.CONTENT_TYPE['SYNCML_WBXML']:
        text = self.wbxml2xml(text)
      #LOG('readResponse, text after wbxml :\n', 0, text)
1081
      xml = Parse(text)
Sebastien Robin's avatar
Sebastien Robin committed
1082
      url = self.getTarget(xml)
1083
      for publication in self.getPublicationList():
1084 1085
        if publication.getPublicationUrl()==url and \
        publication.getTitle()==sync_id:
1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096
          if publication.getActivityEnabled():
            #use activities to send SyncML data.
            self.activate(activity='RAMQueue').PubSync(publication.getPath(),
                                                       text)
            return ' '
          else:
            result = self.PubSync(publication.getPath(),xml)
            # Then encrypt the message
            xml = result['xml']
            #must be commented because this method is alredy called
            #xml = self.sendResponse(xml=xml,domain=publication,send=0)
1097 1098
            if publication.getSyncContentType() == self.CONTENT_TYPE['SYNCML_WBXML']:
              xml = self.xml2wbxml(xml)
1099
            return xml
1100
      
1101
      for subscription in self.getSubscriptionList():
Sebastien Robin's avatar
Sebastien Robin committed
1102 1103
        if subscription.getSubscriptionUrl()==url and \
            subscription.getTitle()==sync_id:
1104 1105 1106 1107
              subscription_path = self.getSubscription(sync_id).getPath()
              self.activate(activity='RAMQueue').SubSync(subscription_path, 
                                                         text)
              return ' '
1108
              #result = self.SubSync(self.getSubscription(sync_id),xml)
1109 1110

    # we use from only if we have a file 
Sebastien Robin's avatar
Sebastien Robin committed
1111
    elif isinstance(from_url, str):
1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124
      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
1125

1126 1127
  security.declareProtected(Permissions.ModifyPortalContent, 
      'getPublicationIdFromTitle')
1128 1129 1130 1131 1132 1133
  def getPublicationIdFromTitle(self, title):
    """
    simply return an id from a title
    """
    return 'pub_' + title

1134 1135
  security.declareProtected(Permissions.ModifyPortalContent, 
      'getPublicationIdFromTitle')
1136 1137 1138 1139 1140 1141
  def getSubscriptionIdFromTitle(self, title):
    """
    simply return an id from a title
    """
    return 'sub_' + title

Sebastien Robin's avatar
Sebastien Robin committed
1142 1143 1144 1145 1146 1147
  security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
  def addNode(self, conduit='ERP5Conduit',**kw):
    """
    """
    # Import the conduit and get it
    from Products.ERP5SyncML import Conduit
1148 1149
    conduit_module = __import__('.'.join([Conduit.__name__, conduit]), 
        globals(), locals(), [''])
Sebastien Robin's avatar
Sebastien Robin committed
1150 1151 1152
    conduit_object = getattr(conduit_module, conduit)()
    return conduit_object.addNode(**kw)

1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189
  def hexdump(self, raw=''):
    """
    this function is used to display the raw in a readable format :
    it display raw in hexadecimal format and display too the printable 
    characters (because if not printable characters are printed, it makes 
    terminal display crash)
    """
    buf = ""
    line = ""
    start = 0
    done = False
    while not done:
        end = start + 16
        max = len(str(raw))
        if end > max:
            end = max
            done = True
        chunk = raw[start:end]
        for i in xrange(len(chunk)):
            if i > 0:
                spacing = " "
            else:
                spacing = ""
            buf += "%s%02x" % (spacing, ord(chunk[i]))
        if done:
            for i in xrange(16 - (end % 16)):
                buf += "   "
        buf += "  "
        for c in chunk:
            val = ord(c)
            if val >= 33 and val <= 126:
                buf += c
            else:
                buf += "."
        buf += "\n"
        start += 16
    return buf 
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1190
InitializeClass( SynchronizationTool )