XMLSyncUtils.py 59.9 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
##############################################################################
#
# 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.
#
##############################################################################

import smtplib
from Products.ERP5SyncML.SyncCode import SyncCode
from Products.ERP5SyncML.Subscription import Signature
32
from StringIO import StringIO
Jean-Paul Smets's avatar
Jean-Paul Smets committed
33
from xml.dom.ext import PrettyPrint
Nicolas Delaby's avatar
Nicolas Delaby committed
34
from ERP5Diff import ERP5Diff
35
import random
Nicolas Delaby's avatar
Nicolas Delaby committed
36
from zLOG import LOG
37 38 39 40 41 42
try:
  from Products.CMFActivity.ActiveObject import ActiveObject
except ImportError:
  LOG('XMLSyncUtils',0,"Can't import ActiveObject")
  class ActiveObject:
    pass
Jean-Paul Smets's avatar
Jean-Paul Smets committed
43
import commands
Nicolas Delaby's avatar
Nicolas Delaby committed
44

45 46 47 48 49 50 51
try:
  from Ft.Xml import Parse
except ImportError:
  LOG('XMLSyncUtils',0,"Can't import Parse")
  class Parse:
    def __init__(self, *args, **kw):
      raise ImportError, "Sorry, it was not possible to import Ft library"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
52

53 54 55 56 57
try:
      from base64 import b16encode, b16decode
except ImportError:
      from base64 import encodestring as b16encode, decodestring as b16decode

58
class XMLSyncUtilsMixin(SyncCode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
59

Sebastien Robin's avatar
Sebastien Robin committed
60
  def SyncMLHeader(self, session_id, msg_id, target, source, target_name=None, 
61 62
      source_name=None, dataCred=None, authentication_format='b64', 
      authentication_type='syncml:auth-basic'):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
63 64 65 66
    """
      Since the Header is always almost the same, this is the
      way to set one quickly.
    """
Sebastien Robin's avatar
Sebastien Robin committed
67 68 69 70 71 72 73 74 75
    xml_list = []
    xml = xml_list.append
    xml(' <SyncHdr>\n')
    xml('  <VerDTD>1.1</VerDTD>\n')
    xml('  <VerProto>SyncML/1.1</VerProto>\n')
    xml('  <SessionID>%s</SessionID>\n' % session_id)
    xml('  <MsgID>%s</MsgID>\n' % msg_id)
    xml('  <Target>\n')
    xml('   <LocURI>%s</LocURI>\n' % target)
76
    if target_name not in (None, ''):
Sebastien Robin's avatar
Sebastien Robin committed
77 78 79 80
      xml('   <LocName>%s</LocName>\n' %target_name)
    xml('  </Target>\n')
    xml('  <Source>\n')
    xml('   <LocURI>%s</LocURI>\n' % source) 
81
    if source_name not in (None, ''):
Sebastien Robin's avatar
Sebastien Robin committed
82 83
      xml('   <LocName>%s</LocName>\n' % source_name)
    xml('  </Source>\n')
84 85 86
    if dataCred not in (None, ''):
      xml('  <Cred>\n')
      xml('   <Meta>\n')
87 88
      xml("    <Format xmlns='syncml:metinf'>%s</Format>\n" % authentication_format)
      xml("    <Type xmlns='syncml:metinf'>%s</Type>\n" % authentication_type)
89 90 91
      xml('   </Meta>\n')
      xml('   <Data>%s</Data>\n' % dataCred)
      xml('  </Cred>\n')
Sebastien Robin's avatar
Sebastien Robin committed
92 93 94
    xml(' </SyncHdr>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
95

Sebastien Robin's avatar
Sebastien Robin committed
96 97
  def SyncMLAlert(self, cmd_id, sync_code, target, source, last_anchor, 
      next_anchor):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
98 99 100 101
    """
      Since the Alert section is always almost the same, this is the
      way to set one quickly.
    """
Sebastien Robin's avatar
Sebastien Robin committed
102 103 104 105 106 107 108 109 110 111 112 113 114
    xml_list = []
    xml = xml_list.append
    xml('  <Alert>\n')
    xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    xml('   <Data>%s</Data>\n' % sync_code)
    xml('   <Item>\n')
    xml('    <Target>\n')
    xml('     <LocURI>%s</LocURI>\n' % target)
    xml('    </Target>\n')
    xml('    <Source>\n')
    xml('     <LocURI>%s</LocURI>\n' % source)
    xml('    </Source>\n')
    xml('    <Meta>\n')
115
    xml('     <Anchor>\n')
Sebastien Robin's avatar
Sebastien Robin committed
116 117 118 119 120 121 122 123
    xml('      <Last>%s</Last>\n' % last_anchor)
    xml('      <Next>%s</Next>\n' % next_anchor)
    xml('     </Anchor>\n')
    xml('    </Meta>\n')
    xml('   </Item>\n')
    xml('  </Alert>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
124

125 126
  def SyncMLStatus(self, remote_xml, data_code, cmd_id, next_anchor, 
      subscription=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
127
    """
128 129
    return a status bloc with all status corresponding to the syncml
    commands in remote_xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
130
    """
131 132 133 134 135

    #list of element in the SyncBody bloc
    syncbody_element_list = remote_xml.xpath('//SyncBody/*')
    
    message_id = self.getMessageId(remote_xml)
Sebastien Robin's avatar
Sebastien Robin committed
136 137
    xml_list = []
    xml = xml_list.append
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203

    if data_code != self.AUTH_REQUIRED:#because for AUTH_REQUIRED, SyncMLChal is                                       #called
      # status for SyncHdr
      message_id = self.getMessageId(remote_xml)
      xml('  <Status>\n')
      xml('   <CmdID>%s</CmdID>\n' % cmd_id)
      cmd_id += 1
      xml('   <MsgRef>%s</MsgRef>\n' % self.getMessageId(remote_xml))
      xml('   <CmdRef>0</CmdRef>\n') #to make reference to the SyncHdr, it's 0
      xml('   <Cmd>SyncHdr</Cmd>\n')
      xml('   <TargetRef>%s</TargetRef>\n' \
        % remote_xml.xpath('string(//SyncHdr/Target/LocURI)').encode('utf-8'))
      xml('   <SourceRef>%s</SourceRef>\n' \
        % remote_xml.xpath('string(//SyncHdr/Source/LocURI)').encode('utf-8'))
      if isinstance(data_code, int):
        data_code = str(data_code)
      xml('   <Data>%s</Data>\n' % data_code)
      xml('  </Status>\n')
    #add the status bloc corresponding to the receive command
    for syncbody_element in syncbody_element_list:
      
      #LOG('SyncMLStatus : ',0,"command:%s, subscription:%s" % (str(syncbody_element.nodeName), subscription))
      if str(syncbody_element.nodeName) not in ('Status', 'Final', 'Get'):
        xml('  <Status>\n')
        xml('   <CmdID>%s</CmdID>\n' % cmd_id)
        cmd_id += 1
        xml('   <MsgRef>%s</MsgRef>\n' % message_id)
        xml('   <CmdRef>%s</CmdRef>\n' \
            % syncbody_element.xpath('string(.//CmdID)').encode('utf-8'))
        xml('   <Cmd>%s</Cmd>\n' % syncbody_element.nodeName.encode('utf-8'))

        target_ref = syncbody_element.xpath('string(.//Target/LocURI)').encode('utf-8')
        if target_ref not in (None, ''):
          xml('   <TargetRef>%s</TargetRef>\n' % target_ref )
        source_ref = syncbody_element.xpath('string(.//Source/LocURI)').encode('utf-8')
        if source_ref not in (None, ''):
          xml('   <SourceRef>%s</SourceRef>\n' % source_ref )

        #xml('   <Data>%s</Data>\n' % subscriber.getSynchronizationType())
        if syncbody_element.nodeName.encode('utf-8') == 'Add':
          xml('   <Data>%s</Data>\n' % str(self.ITEM_ADDED))
        elif syncbody_element.nodeName.encode('utf-8') == 'Alert' and \
            syncbody_element.xpath('string(.//Data)').encode('utf-8') == \
            str(self.SLOW_SYNC):
          xml('   <Data>%s</Data>\n' % str(self.REFRESH_REQUIRED))
        else:
          xml('   <Data>%s</Data>\n' % str(self.SUCCESS))

       # if syncbody_element.xpath('.//Item') not in ([], None, '') and\
       #     syncbody_element.xpath('.//Item.....'): #contient une ancre Next...
        if str(syncbody_element.nodeName) == 'Alert':
          xml('   <Item>\n')
          xml('    <Data>\n')
          xml('     <Anchor>\n')
          xml('      <Next>%s</Next>\n' % next_anchor)
          xml('     </Anchor>\n')
          xml('    </Data>\n')
          xml('   </Item>\n')
        xml('  </Status>\n')

      if str(syncbody_element.nodeName) == 'Get' and subscription != None:
        cmd_ref = syncbody_element.xpath('string(.//CmdID)').encode('utf-8')
        syncml_result = self.SyncMLPut(cmd_id, subscription, markup='Results', 
            cmd_ref=cmd_ref, message_id=self.getMessageId(remote_xml))
        xml(syncml_result)
        cmd_id += 1
Sebastien Robin's avatar
Sebastien Robin committed
204
    xml_a = ''.join(xml_list)
205
    return {'xml':xml_a, 'cmd_id':cmd_id}
Jean-Paul Smets's avatar
Jean-Paul Smets committed
206

207 208 209
  def SyncMLConfirmation(self, cmd_id=None, target_ref=None, cmd=None, 
      sync_code=None, msg_ref=None, cmd_ref=None, source_ref=None, 
      remote_xml=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
210
    """
Sebastien Robin's avatar
Sebastien Robin committed
211
    This is used in order to confirm that an object was correctly
Jean-Paul Smets's avatar
Jean-Paul Smets committed
212 213
    synchronized
    """
214 215 216 217 218 219
    if remote_xml is not None :
      msg_ref=remote_xml.xpath("string(//MsgID)").encode('utf-8')
      cmd_ref=remote_xml.xpath("string(.//CmdID)").encode('utf-8')
      target_ref=remote_xml.xpath("string(.//Target/LocURI)").encode('utf-8')
      source_ref=remote_xml.xpath("string(.//Source/LocURI)").encode('utf-8')

Sebastien Robin's avatar
Sebastien Robin committed
220 221 222
    xml_list = []
    xml = xml_list.append
    xml('  <Status>\n')
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
    #here there is a lot of test to keep compatibility with older call
    if cmd_id not in (None,'') :
      xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    if msg_ref not in (None,''):
      xml('   <MsgRef>%s</MsgRef>\n' % msg_ref)
    if cmd_ref not in (None,''):
      xml('   <CmdRef>%s</CmdRef>\n' %cmd_ref)
    if cmd not in (None,''):
      xml('   <Cmd>%s</Cmd>\n' % cmd)
    if target_ref not in (None,''):
      xml('   <TargetRef>%s</TargetRef>\n' % target_ref)
    if source_ref not in (None,''):
      xml('   <SourceRef>%s</SourceRef>\n' % source_ref)
    if sync_code not in (None,''):
      xml('   <Data>%s</Data>\n' % sync_code)
Sebastien Robin's avatar
Sebastien Robin committed
238 239 240 241 242 243 244 245 246 247 248 249 250
    xml('  </Status>\n')
    xml_a = ''.join(xml_list)
    return xml_a
    
  def SyncMLChal(self, cmd_id, cmd, target_ref, source_ref, auth_format, 
      auth_type, data_code):
    """
    This is used in order to ask crendentials
    """
    xml_list = []
    xml = xml_list.append
    xml('  <Status>\n')
    xml('   <CmdID>%s</CmdID>\n' % cmd_id)
251 252
    xml('   <MsgRef>1</MsgRef>\n')
    xml('   <CmdRef>0</CmdRef>\n')
Sebastien Robin's avatar
Sebastien Robin committed
253 254 255 256 257
    xml('   <Cmd>%s</Cmd>\n' % cmd)
    xml('   <TargetRef>%s</TargetRef>\n' % target_ref)
    xml('   <SourceRef>%s</SourceRef>\n' % source_ref)
    xml('   <Chal>\n')
    xml('    <Meta>\n')
258 259
    xml("     <Format xmlns='syncml:metinf'>%s</Format>\n" % auth_format)
    xml("     <Type xmlns='syncml:metinf'>%s</Type>\n" % auth_type)
Sebastien Robin's avatar
Sebastien Robin committed
260 261 262 263 264 265
    xml('    </Meta>\n')
    xml('   </Chal>\n')
    xml('   <Data>%s</Data>\n' % str(data_code))
    xml('  </Status>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
266

267 268
  def SyncMLPut(self, cmd_id, subscription, markup='Put', cmd_ref=None, 
      message_id=None):
269 270
    """
    this is used to inform the server of the CTType version supported
271 272
    but if the server use it to respond to a Get request, it's a <Result> markup
    instead of <Put>
273 274 275 276 277 278 279 280 281 282 283 284 285 286
    """
    from Products.ERP5SyncML import Conduit
    # Import the conduit and get it
    conduit_name = subscription.getConduit()
    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)()
    #if the conduit support the SyncMLPut :
287
    if hasattr(conduit, 'getCapabilitiesCTTypeList') and \
288 289 290 291
        hasattr(conduit, 'getCapabilitiesVerCTList') and \
        hasattr(conduit, 'getPreferedCapabilitieVerCT'):
      xml_list = []
      xml = xml_list.append
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
      xml('  <%s>\n' % markup)
      xml('   <CmdID>%s</CmdID>\n' % cmd_id)
      if message_id not in (None, ''):
        xml('   <MsgRef>%s</MsgRef>\n' % message_id)
      if cmd_ref not in (None, '') :
        xml('   <CmdRef>%s</CmdRef>\n' % cmd_ref)
      xml('   <Meta>\n')
      xml('    <Type>application/vnd.syncml-devinf+xml</Type>\n');
      xml('   </Meta>\n')
      xml('   <Item>\n')
      xml('    <Source>\n')
      xml('     <LocURI>./devinf11</LocURI>\n')
      xml('    </Source>\n')
      xml('    <Data>\n')
      xml('     <DevInf>\n')
      xml('      <VerDTD>1.1</VerDTD>\n')
      xml('      <Man>Nexedi</Man>\n')
      xml('      <Mod>ERP5SyncML</Mod>\n')
      xml('      <OEM>Open Source</OEM>\n')
      xml('      <SwV>0.1</SwV>\n')
      xml('      <DevID>%s</DevID>\n' % subscription.getSubscriptionUrl())
      xml('      <DevTyp>workstation</DevTyp>\n')
      xml('      <UTC/>\n')
      xml('      <DataStore>\n')
      xml('       <SourceRef>%s</SourceRef>\n' % subscription.getSourceURI())
      xml('       <Rx-Pref>\n')
      xml('        <CTType>%s</CTType>\n' % \
          conduit.getPreferedCapabilitieCTType())
      xml('        <VerCT>%s</VerCT>\n' % \
          conduit.getPreferedCapabilitieVerCT())
      xml('       </Rx-Pref>\n')
      for type in conduit.getCapabilitiesCTTypeList():
        if type != self.MEDIA_TYPE['TEXT_XML']:
          for rx_version in conduit.getCapabilitiesVerCTList(type):
            xml('       <Rx>\n')
            xml('        <CTType>%s</CTType>\n' % type)
            xml('        <VerCT>%s</VerCT>\n' % rx_version)
            xml('       </Rx>\n')

      xml('       <Tx-Pref>\n')
      xml('        <CTType>%s</CTType>\n' % \
          conduit.getPreferedCapabilitieCTType())
      xml('        <VerCT>%s</VerCT>\n' % \
          conduit.getPreferedCapabilitieVerCT())
      xml('       </Tx-Pref>\n')
      for type in conduit.getCapabilitiesCTTypeList():
        if type != self.MEDIA_TYPE['TEXT_XML']:
          for tx_version in conduit.getCapabilitiesVerCTList(type):
            xml('       <Tx>\n')
            xml('        <CTType>%s</CTType>\n' % type)
            xml('        <VerCT>%s</VerCT>\n' % tx_version)
            xml('       </Tx>\n')

      xml('       <SyncCap>\n')
      xml('        <SyncType>2</SyncType>\n')
      xml('        <SyncType>1</SyncType>\n')
      xml('        <SyncType>4</SyncType>\n')
      xml('        <SyncType>6</SyncType>\n')
      xml('       </SyncCap>\n')

      xml('      </DataStore>\n')
      xml('     </DevInf>\n')
      xml('    </Data>\n')
      xml('   </Item>\n')
      xml('  </%s>\n' % markup)
357 358 359 360 361
      xml_a = ''.join(xml_list)
      return xml_a
    return ''


Jean-Paul Smets's avatar
Jean-Paul Smets committed
362 363 364 365 366 367 368 369 370
  def sendMail(self, fromaddr, toaddr, id_sync, msg):
    """
      Send a message via email
      - sync_object : this is a publication or a subscription
      - message : what we want to send
    """
    header = "Subject: %s\n" % id_sync
    header += "To: %s\n\n" % toaddr
    msg = header + msg
Nicolas Delaby's avatar
Nicolas Delaby committed
371
    #LOG('SubSendMail',0,'from: %s, to: %s' % (fromaddr,toaddr))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
372 373 374
    server = smtplib.SMTP('localhost')
    server.sendmail(fromaddr, toaddr, msg)
    # if we want to send the email to someone else (debugging)
375
    #server.sendmail(fromaddr, "seb@localhost", msg)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
376 377
    server.quit()

378
  def addXMLObject(self, cmd_id=0, object=None, xml_string=None,
379
                  more_data=0,gid=None, media_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
380 381 382
    """
      Add an object with the SyncML protocol
    """
Sebastien Robin's avatar
Sebastien Robin committed
383 384 385 386
    xml_list = []
    xml = xml_list.append
    xml('   <Add>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
387
    xml('    <Meta>\n')
388
    xml('     <Type>%s</Type>\n' % media_type)
389
    xml('    </Meta>\n')
Sebastien Robin's avatar
Sebastien Robin committed
390 391 392 393
    xml('    <Item>\n')
    xml('     <Source>\n')
    xml('      <LocURI>%s</LocURI>\n' % gid)
    xml('     </Source>\n')
394 395 396 397 398 399 400 401
    if media_type == self.MEDIA_TYPE['TEXT_XML']:
      xml('     <Data>')
      xml(xml_string)
      xml('</Data>\n')
    else:
      xml('     <Data><![CDATA[')
      xml(xml_string)
      xml('\n]]></Data>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
402
    if more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
403 404 405 406 407
      xml('     <MoreData/>\n')
    xml('    </Item>\n')
    xml('   </Add>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
408

409
  def deleteXMLObject(self, cmd_id=0, object_gid=None, rid=None, xml_object=''):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
410
    """
Sebastien Robin's avatar
Sebastien Robin committed
411 412 413 414 415 416 417
      Delete an object with the SyncML protocol
    """
    xml_list = []
    xml = xml_list.append
    xml('   <Delete>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
    xml('    <Item>\n')
418 419 420 421 422 423 424 425
    if rid not in (None, ''):
      xml('     <Target>\n')
      xml('      <LocURI>%s</LocURI>\n' % rid)
      xml('     </Target>\n')
    else:
      xml('     <Source>\n')
      xml('      <LocURI>%s</LocURI>\n' % object_gid)
      xml('     </Source>\n')
426 427
    #xml('     <Data>\n')  #this 2 lines seems to be useless
    #xml('     </Data>\n')
Sebastien Robin's avatar
Sebastien Robin committed
428 429 430 431
    xml('    </Item>\n')
    xml('   </Delete>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
432

433
  def replaceXMLObject(self, cmd_id=0, object=None, xml_string=None,
434
                       more_data=0, gid=None, rid=None, media_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
435
    """
Sebastien Robin's avatar
Sebastien Robin committed
436 437 438 439 440 441
      Replace an object with the SyncML protocol
    """
    xml_list = []
    xml = xml_list.append
    xml('   <Replace>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
442
    xml('    <Meta>\n')
443
    xml('     <Type>%s</Type>\n' % media_type)
444
    xml('    </Meta>\n')
Sebastien Robin's avatar
Sebastien Robin committed
445
    xml('    <Item>\n')
446 447 448 449 450 451 452 453
    if rid is not None:
      xml('     <Target>\n')
      xml('      <LocURI>%s</LocURI>\n' % str(rid))
      xml('     </Target>\n')
    else:
      xml('     <Source>\n')
      xml('      <LocURI>%s</LocURI>\n' % str(gid))
      xml('     </Source>\n')
454
    xml('     <Data>')
Sebastien Robin's avatar
Sebastien Robin committed
455 456
    xml(xml_string)
    xml('     </Data>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
457
    if more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
458 459 460 461 462
      xml('     <MoreData/>\n')
    xml('    </Item>\n')
    xml('   </Replace>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
463

Nicolas Delaby's avatar
Nicolas Delaby committed
464
  def getXupdateObject(self, object_xml=None, old_xml=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
465 466
    """
    Generate the xupdate with the new object and the old xml
Nicolas Delaby's avatar
Nicolas Delaby committed
467 468 469 470 471 472 473 474 475 476 477 478 479
    """
    erp5diff = ERP5Diff()
    erp5diff.compare(old_xml, object_xml)
    xupdate_doc = erp5diff._result
    #minidom is buggy, add namespace declaration, and version
    attr_ns = xupdate_doc.createAttribute('xmlns:xupdate')
    attr_ns.value = 'http://www.xmldb.org/xupdate'
    attr_version = xupdate_doc.createAttribute('version')
    attr_version.value='1.0'
    xupdate_doc.documentElement.setAttributeNode(attr_ns)
    xupdate_doc.documentElement.setAttributeNode(attr_version)
    xupdate = xupdate_doc.toxml()
    #omit xml declaration
Jean-Paul Smets's avatar
Jean-Paul Smets committed
480 481 482
    xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
    return xupdate

483 484 485 486 487
  def getSessionId(self, xml):
    """
    We will retrieve the session id of the message
    """
    session_id = 0
488 489
    session_id = xml.xpath('string(/SyncML/SyncHdr/SessionID)')
    session_id = int(session_id)
490
    return session_id
Sebastien Robin's avatar
Sebastien Robin committed
491 492 493 494 495 496
    
  def getMessageId(self, xml):
    """
    We will retrieve the message id of the message
    """
    message_id = 0
497 498
    message_id = xml.xpath('string(/SyncML/SyncHdr/MsgID)')
    message_id = int(message_id)
Sebastien Robin's avatar
Sebastien Robin committed
499 500 501 502 503 504 505
    return message_id

  def getTarget(self, xml):
    """
    return the target in the SyncHdr section
    """
    url = ''
506 507
    url = xml.xpath('string(/SyncML/SyncHdr/Target/LocURI)')
    url = url.encode('utf-8')
Sebastien Robin's avatar
Sebastien Robin committed
508 509
    return url

Jean-Paul Smets's avatar
Jean-Paul Smets committed
510 511 512 513 514
  def getAlertLastAnchor(self, xml_stream):
    """
      Return the value of the last anchor, in the
      alert section of the xml_stream
    """
515
    last_anchor = xml_stream.xpath('string(.//Alert/Item/Meta/Anchor/Last)')
516
    last_anchor = last_anchor.encode('utf-8')
Sebastien Robin's avatar
Sebastien Robin committed
517
    return last_anchor
Jean-Paul Smets's avatar
Jean-Paul Smets committed
518 519 520 521 522 523

  def getAlertNextAnchor(self, xml_stream):
    """
      Return the value of the next anchor, in the
      alert section of the xml_stream
    """
524
    next_anchor = xml_stream.xpath('string(.//Alert/Item/Meta/Anchor/Next)')
525 526
    next_anchor = next_anchor.encode('utf-8')
    return next_anchor
Jean-Paul Smets's avatar
Jean-Paul Smets committed
527

528
  def getSourceURI(self, xml):
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
    """
    return the source uri of the data base
    """
    source_uri = xml.xpath('string(//SyncBody/Alert/Item/Source/LocURI)')
    if isinstance(source_uri, unicode):
      source_uri = source_uri.encode('utf-8')
    return source_uri
  
  def getTargetURI(self, xml):
    """
    return the target uri of the data base
    """
    target_uri = xml.xpath('string(//SyncBody/Alert/Item/Target/LocURI)')
    if isinstance(target_uri, unicode):
      target_uri = target_uri.encode('utf-8')
    return target_uri

  def getSubscriptionUrl(self, xml):
547 548 549 550 551 552 553 554
    """
    return the source URI of the syncml header
    """
    subscription_url = xml.xpath('string(//SyncHdr/Source/LocURI)')
    if isinstance(subscription_url, unicode):
      subscription_url = subscription_url.encode('utf-8')
    return subscription_url
  
Jean-Paul Smets's avatar
Jean-Paul Smets committed
555 556 557 558
  def getStatusTarget(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
559 560 561 562
    status = xml.xpath('string(TargetRef)')
    if isinstance(status, unicode):
      status = status.encode('utf-8')
    return status
Jean-Paul Smets's avatar
Jean-Paul Smets committed
563 564 565 566 567

  def getStatusCode(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
568 569 570
    status_code = xml.xpath('string(Data)')
    if status_code not in ('', None, []):
      return int(status_code)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
571 572
    return None

573 574 575 576 577
  def getStatusCommand(self, xml):
    """
      Return the value of the command inside the xml_stream
    """
    if xml.nodeName=='Status':
578 579 580 581
      cmd = xml.xpath('string(//Status/Cmd)')
      if isinstance(cmd, unicode):
        cmd = cmd.encode('utf-8')
      return cmd
582 583
    else:
      return None
584

585 586 587
  def getCred(self, xml):
    """
      return the credential information : type, format and data
Nicolas Delaby's avatar
Nicolas Delaby committed
588
    """
589 590 591 592 593
    format=''
    type=''
    data=''

    first_node = xml.childNodes[0]
594 595
    format = first_node.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Format'])")
    type = first_node.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Type'])")
596
    data = first_node.xpath('string(/SyncML/SyncHdr/Cred/Data)')
597

598 599 600
    format = format.encode('utf-8')
    type = type.encode('utf-8')
    data = data.encode('utf-8')
601 602
    return (format, type, data)

603
  def checkCred(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
604
    """
605
      Check if there's a Cred section in the xml_stream
Jean-Paul Smets's avatar
Jean-Paul Smets committed
606
    """
607 608 609
    if xml_stream.xpath('string(SyncML/SyncHdr/Cred)') not in ('', None, []):
      return True
    return False
Jean-Paul Smets's avatar
Jean-Paul Smets committed
610

611
  def getChal(self, xml):
Sebastien Robin's avatar
Sebastien Robin committed
612
    """
613 614
      return the chalenge information : format and type
    """    
615 616
    format=None
    type=None
617 618

    first_node = xml.childNodes[0]
619 620
    format = first_node.xpath("string(//*[local-name() = 'Format'])")
    type = first_node.xpath("string(//*[local-name() = 'Type'])")
621 622 623 624 625 626

    format = format.encode('utf-8')
    type = type.encode('utf-8')
    return (format, type)

  def checkChal(self, xml_stream):
Sebastien Robin's avatar
Sebastien Robin committed
627
    """
628 629 630 631 632 633 634
      Check if there's a Chal section in the xml_stream
    """
    if xml_stream.xpath('string(SyncML/SyncBody/Status/Chal)') \
        not in ('', None, []):
      return True
    return False

635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
  def checkMap(self, xml_stream):
    """
      Check if there's a Map section in the xml_stream
    """
    if xml_stream.xpath('string(SyncML/SyncBody/Map)') \
        not in ('', None, []):
      return True
    return False

  def setRidWithMap(self, xml_stream, subscriber):
    """
      get all the local objects of the given target id and set them the rid with
      the given source id (in the Map section)
    """
    item_list = xml_stream.xpath('SyncML/SyncBody/Map/MapItem')
    for map_item in item_list:
      gid = map_item.xpath('string(.//Target/LocURI)').encode('utf-8')
      signature = subscriber.getSignatureFromGid(gid)
      rid = map_item.xpath('string(.//Source/LocURI)').encode('utf-8')
      signature.setRid(rid)   

656 657 658 659 660 661 662 663 664
  def getAlertCode(self, xml_stream):
    """
      Return the value of the alert code inside the full syncml message
    """
    alert_code = xml_stream.xpath('string(SyncML/SyncBody/Alert/Data)')
    if alert_code not in (None, ''):
      return int(alert_code)
    else:
      return None
Sebastien Robin's avatar
Sebastien Robin committed
665

Jean-Paul Smets's avatar
Jean-Paul Smets committed
666 667
  def checkAlert(self, xml_stream):
    """
Sebastien Robin's avatar
Sebastien Robin committed
668
      Check if there's an Alert section in the xml_stream
Jean-Paul Smets's avatar
Jean-Paul Smets committed
669
    """
670 671 672
    alert = False
    if xml_stream.xpath('string(SyncML/SyncBody/Alert)') not in ('', None, []):
      alert = True
Jean-Paul Smets's avatar
Jean-Paul Smets committed
673 674 675 676 677 678
    return alert

  def checkSync(self, xml_stream):
    """
      Check if there's an Sync section in the xml_xtream
    """
679 680 681 682
    sync = False
    if xml_stream.xpath('string(SyncML/SyncBody/Sync)') not in ('', None, []):
      sync = True
    return sync
Jean-Paul Smets's avatar
Jean-Paul Smets committed
683

684
  def checkStatus(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
685 686 687
    """
      Check if there's a Status section in the xml_xtream
    """
688 689 690
    status = False
    if xml_stream.xpath('string(SyncML/SyncBody/Status)') not in ('', None, []):
      status = True
Jean-Paul Smets's avatar
Jean-Paul Smets committed
691 692
    return status

693
  def getSyncActionList(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
694
    """
695
    return the list of the action (could be "add", "replace", "delete").
Jean-Paul Smets's avatar
Jean-Paul Smets committed
696
    """
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
    return xml_stream.xpath('//Add|//Delete|//Replace')

  def getSyncBodyStatusList(self, xml_stream):
    """
    return the list of dictionary corredponding to the data of each status bloc
    the data are : cmd, code and source
    """
    status_list = []
    xml = xml_stream.xpath('//Status')
    for status in xml:
      tmp_dict = {}
      tmp_dict['cmd']     = status.xpath('string(./Cmd)').encode('utf-8')
      tmp_dict['code']    = status.xpath('string(./Data)').encode('utf-8')
      tmp_dict['source']  = status.xpath('string(./SourceRef)').encode('utf-8')
      tmp_dict['target']  = status.xpath('string(./TargetRef)').encode('utf-8')
      status_list.append(tmp_dict)
    return status_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
714

715 716 717 718
  def getDataText(self, action):
    """
    return the section data in text form, it's usefull for the VCardConduit
    """
719 720 721 722
    data = action.xpath('string(Item/Data)')
    if isinstance(data, unicode):
      data = data.encode('utf-8')
    return data
723

Jean-Paul Smets's avatar
Jean-Paul Smets committed
724 725 726 727
  def getDataSubNode(self, action):
    """
      Return the node starting with <object....> of the action
    """
728 729 730 731
    if action.xpath('.//Item/Data') not in ([], None):
      data_node = action.xpath('.//Item/Data')[0]
      if data_node.hasChildNodes():
        return data_node.childNodes[0]
732
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
733 734 735 736 737

  def getPartialData(self, action):
    """
      Return the node starting with <object....> of the action
    """
738 739 740
    comment_list = action.xpath('.//Item/Data[comment()]')
    if comment_list != []:
      return comment_list[0].childNodes[0].data.encode('utf-8')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
741 742
    return None

743
  def getActionId(self, action, action_name):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
744 745 746
    """
      Return the rid of the object described by the action
    """
747 748 749 750 751 752
    id = action.xpath('string(.//Item/Source/LocURI)')
    if id in (None, ''):
      id = action.xpath('string(.//Item/Target/LocURI)')
    if isinstance(id, unicode):
      id = id.encode('utf-8')
    return id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
753 754 755 756 757

  def checkActionMoreData(self, action):
    """
      Return the rid of the object described by the action
    """
758 759 760
    if action.xpath('Item/MoreData') not in ([],None) :
      return True
    return False
Jean-Paul Smets's avatar
Jean-Paul Smets committed
761 762 763 764 765

  def getActionType(self, action):
    """
      Return the type of the object described by the action
    """
766 767 768 769
    action_type = action.xpath('string(Meta/Type)')
    if isinstance(action_type, unicode):
      action_type = action_type.encode('utf-8')
    return action_type
Jean-Paul Smets's avatar
Jean-Paul Smets committed
770

771 772 773 774
  def getElementNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
775
    return node.xpath('*')
776

777 778 779 780 781
  def getTextNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
    subnode_list = []
782
    for subnode in node.childNodes or []:
783 784 785
      if subnode.nodeType == subnode.TEXT_NODE:
        subnode_list += [subnode]
    return subnode_list
786
  
787 788 789 790
  def getAttributeNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
791
    return node.xpath('@*')
792

793 794
  def getSyncMLData(self, domain=None, remote_xml=None, cmd_id=0,
                    subscriber=None, xml_confirmation=None, conduit=None,
Nicolas Delaby's avatar
Nicolas Delaby committed
795
                    max=None, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
796
    """
797 798
    This generate the syncml data message. This returns a string
    with all modification made locally (ie replace, add ,delete...)
799 800 801

    if object is not None, this usually means we want to set the
    actual xupdate on the signature.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
802
    """
803
    LOG('getSyncMLData starting...',0,'')
Sebastien Robin's avatar
Sebastien Robin committed
804 805
    if isinstance(conduit, str):
      conduit = self.getConduitByName(conduit)
806
    local_gid_list = []
807
    syncml_data = kw.get('syncml_data','')
Nicolas Delaby's avatar
Nicolas Delaby committed
808
    result = {'finished':1}
809 810
    if isinstance(remote_xml, str) or isinstance(remote_xml, unicode):
      remote_xml = Parse(remote_xml)
811
    if subscriber.getRemainingObjectPathList() is None:
812
      object_list = domain.getObjectList()
813 814
      object_path_list = map(lambda x: x.getPhysicalPath(),object_list)
      subscriber.setRemainingObjectPathList(object_path_list)
815 816 817

      local_gid_list = map(lambda x: domain.getGidFromObject(x),object_list)
      # Objects to remove
818
      #LOG('remove object to remove ...',0,'')
819
      for object_gid in subscriber.getGidList():
Nicolas Delaby's avatar
Nicolas Delaby committed
820
        if object_gid not in local_gid_list:
821
          # This is an object to remove
822
          signature = subscriber.getSignatureFromGid(object_gid)
823 824 825
          if signature.getStatus()!=self.PARTIAL: # If partial, then we have a signature
                                                  # but no local object
            xml_object = signature.getXML()
826 827
            if xml_object is not None: # This prevent to delete an object that
                                      # we were not able to create
828
              rid = signature.getRid()
829 830
              #if rid != None:
              #  object_gid=rid #to use the remote id if it exist
831
              syncml_data += self.deleteXMLObject(xml_object=signature.getXML()\
832
                  or '', object_gid=object_gid, rid=rid,cmd_id=cmd_id)
833
              cmd_id += 1
Nicolas Delaby's avatar
Nicolas Delaby committed
834 835 836 837
      #delete Signature if object does not exist anymore
      for known_gid in subscriber.getGidList():
        if known_gid not in local_gid_list:
          subscriber.delSignature(known_gid)
838
    local_gid_list = []
839
    loop = 0
840
    for object_path in subscriber.getRemainingObjectPathList():
Nicolas Delaby's avatar
Nicolas Delaby committed
841 842 843
      if max is not None and loop >= max:
        result['finished'] = 0
        break
844
      object = self.unrestrictedTraverse(object_path)
845
      status = self.SENT
846
      object_gid = domain.getGidFromObject(object)
847
      if object_gid in ('', None):
Nicolas Delaby's avatar
Nicolas Delaby committed
848
        continue
849
      local_gid_list += [object_gid]
850
      force = 0
Nicolas Delaby's avatar
Nicolas Delaby committed
851
      if syncml_data.count('\n') < self.MAX_LINES and not \
852 853
          object.id.startswith('.'):
        # If not we have to cut
Nicolas Delaby's avatar
Nicolas Delaby committed
854 855
        #LOG('getSyncMLData',0,'object_path: %s' % '/'.join(object_path))
        #LOG('getSyncMLData',0,'xml_mapping: %s' % str(domain.getXMLMapping()))
856 857
        #LOG('getSyncMLData',0,'code: %s' % str(self.getAlertCode(remote_xml)))
        #LOG('getSyncMLData',0,'gid_list: %s' % str(local_gid_list))
858
        #LOG('getSyncMLData',0,'subscriber.getGidList: %s' % subscriber.getGidList())
859 860
        #LOG('getSyncMLData',0,'hasSignature: %s' % str(subscriber.hasSignature(object_gid)))
        #LOG('getSyncMLData',0,'alert_code == slowsync: %s' % str(self.getAlertCode(remote_xml)==self.SLOW_SYNC))
861

Nicolas Delaby's avatar
Nicolas Delaby committed
862 863 864 865
        signature = subscriber.getSignatureFromGid(object_gid)
        ## Here we first check if the object was modified or not by looking at dates
        #if signature is not None:
          #LOG('getSyncMLData',0,'signature.getStatus: %s' % signature.getStatus())
866 867 868
        status = self.SENT
        more_data=0
        # For the case it was never synchronized, we have to send everything
Nicolas Delaby's avatar
Nicolas Delaby committed
869
        if signature is not None and signature.getXMLMapping() is None:
870
          pass
Nicolas Delaby's avatar
Nicolas Delaby committed
871
        elif signature is None or (signature.getXML() is None and \
872 873
            signature.getStatus() != self.PARTIAL) or \
            self.getAlertCode(remote_xml) == self.SLOW_SYNC:
Nicolas Delaby's avatar
Nicolas Delaby committed
874
          #LOG('getSyncMLData',0,'Current object.getPath: %s' % object.getPath())
875
          xml_object = domain.getXMLFromObject(object)
876
          xml_string = xml_object
877 878 879 880
          if isinstance(xml_string, unicode):
            xml_string = xml_object.encode('utf-8')
          gid = subscriber.getGidFromObject(object)
          signature = Signature(id=gid,object=object)
881 882
          signature.setTempXML(xml_object)
          if xml_string.count('\n') > self.MAX_LINES:
883
            if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
884
              xml_string = xml_string.replace('--','@-@@-@')
885 886 887 888 889 890 891 892
            more_data=1
            i = 0
            short_string = ''
            rest_string = xml_string
            while i < self.MAX_LINES:
              short_string += rest_string[:rest_string.find('\n')+1]
              rest_string = xml_string[len(short_string):]
              i += 1
893
            #LOG('getSyncMLData',0,'setPartialXML with: %s' % str(rest_string))
894
            signature.setPartialXML(rest_string)
895
            status = self.PARTIAL
896 897
            signature.setAction('Add')
            xml_string = '<!--' + short_string + '-->'
898 899 900
          gid = signature.getRid()#in fisrt, we try with rid if there is one
          if gid == None:
            gid = signature.getGid()
Nicolas Delaby's avatar
Nicolas Delaby committed
901 902
          syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object,
              gid=gid, xml_string=xml_string,
903
              more_data=more_data, media_type=subscriber.getMediaType())
904 905 906 907 908
          cmd_id += 1
          signature.setStatus(status)
          subscriber.addSignature(signature)
        elif signature.getStatus()==self.NOT_SYNCHRONIZED \
            or signature.getStatus()==self.PUB_CONFLICT_MERGE: # We don't have synchronized this object yet
909
          xml_object = domain.getXMLFromObject(object)
910 911
          #LOG('getSyncMLData',0,'checkMD5: %s' % str(signature.checkMD5(xml_object)))
          #LOG('getSyncMLData',0,'getStatus: %s' % str(signature.getStatus()))
912
          if signature.getStatus()==self.PUB_CONFLICT_MERGE:
913 914 915
            xml_confirmation += self.SyncMLConfirmation(cmd_id=cmd_id, 
                source_ref=signature.getGid(), sync_code=self.CONFLICT_MERGE, 
                cmd='Replace')
916
          set_synchronized = 1
917
          if not signature.checkMD5(xml_object):
918
            set_synchronized = 0
919
            # This object has changed on this side, we have to generate some xmldiff
920 921
            if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']:
              xml_string = self.getXupdateObject(
922 923
                                      domain.getXMLFromObject(object),
                                      signature.getXML())
924 925
            else: #if there is no xml, we re-send all the object
              xml_string=domain.getXMLFromObject(object)
926
            if xml_string.count('\n') > self.MAX_LINES:
927 928
              # This make comment fails, so we need to replace
              if xml_string.find('--') >= 0:
929
                xml_string = xml_string.replace('--','@-@@-@')
930 931 932 933 934 935 936 937 938 939 940 941 942
              i = 0
              more_data=1
              short_string = ''
              rest_string = xml_string
              while i < self.MAX_LINES:
                short_string += rest_string[:rest_string.find('\n')+1]
                rest_string = xml_string[len(short_string):]
                i += 1
              signature.setPartialXML(rest_string)
              status = self.PARTIAL
              signature.setAction('Replace')
              xml_string = '<!--' + short_string + '-->'
            signature.setStatus(status)
943
            if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
944
              xml_string = domain.getXMLFromObject(object)
945 946
            rid = signature.getRid()#in fisrt, we try with rid if there is one
            gid = signature.getGid()
947
            syncml_data += self.replaceXMLObject(cmd_id=cmd_id, object=object,
948 949
                                                 gid=gid, rid=rid, 
                                                 xml_string=xml_string,
950 951
                                                 more_data=more_data,
                                                 media_type=subscriber.getMediaType())
952 953
            cmd_id += 1
            signature.setTempXML(xml_object)
954 955
          # Now we can apply the xupdate from the subscriber
          subscriber_xupdate = signature.getSubscriberXupdate()
956
          #LOG('getSyncMLData subscriber_xupdate',0,subscriber_xupdate)
957 958
          if subscriber_xupdate is not None:
            old_xml = signature.getXML()
959 960
            conduit.updateNode(xml=subscriber_xupdate, object=object,
                previous_xml=old_xml, force=(domain.getDomainType() == self.SUB),
961
                simulate=0)
962
            xml_object = domain.getXMLFromObject(object)
963 964 965
            signature.setTempXML(xml_object)
          if set_synchronized: # We have to do that after this previous update
            # We should not have this case when we are in CONFLICT_MERGE
966 967 968
            signature.setStatus(self.SYNCHRONIZED)
        elif signature.getStatus()==self.PUB_CONFLICT_CLIENT_WIN:
          # We have decided to apply the update
969
          # XXX previous_xml will be geXML instead of getTempXML because
970 971
          # some modification was already made and the update
          # may not apply correctly
972
          xml_update = signature.getPartialXML()
973 974
          conduit.updateNode(xml=signature.getPartialXML(), object=object,
                            previous_xml=signature.getXML(),force=1)
975 976 977
          xml_confirmation += self.SyncMLConfirmation(cmd_id=cmd_id, 
              target_ref=object_gid, sync_code=self.CONFLICT_CLIENT_WIN,
              cmd='Replace')
978 979 980 981 982 983 984 985 986 987 988 989 990 991 992
          signature.setStatus(self.SYNCHRONIZED)
        elif signature.getStatus()==self.PARTIAL:
          xml_string = signature.getPartialXML()
          if xml_string.count('\n') > self.MAX_LINES:
            i = 0
            more_data=1
            short_string = ''
            rest_string = xml_string
            while i < self.MAX_LINES:
              short_string += rest_string[:rest_string.find('\n')+1]
              rest_string = xml_string[len(short_string):]
              i += 1
            signature.setPartialXML(rest_string)
            xml_string = short_string
            status = self.PARTIAL
993
          if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
994
            xml_string = xml_string.replace('--','@-@@-@')
995 996
          xml_string = '<!--' + xml_string + '-->'
          signature.setStatus(status)
997
          if(subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']):
998
            xml_string = domain.getXMLFromObject(object)
Nicolas Delaby's avatar
Nicolas Delaby committed
999
          #LOG('xml_string =', 0, xml_string)
1000
          if signature.getAction()=='Replace':
1001 1002
            rid = signature.getRid()#in fisrt, we try with rid if there is one
            gid = signature.getGid()
1003
            syncml_data += self.replaceXMLObject(cmd_id=cmd_id, object=object,
1004
                gid=gid, rid=rid, xml_string=xml_string, more_data=more_data,
1005
                media_type=subscriber.getMediaType())
1006
          elif signature.getAction()=='Add':
1007 1008 1009
            gid = signature.getRid()#in fisrt, we try with rid if there is one
            if gid == None:
              gid = signature.getGid()
Nicolas Delaby's avatar
Nicolas Delaby committed
1010 1011
            syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object,
                gid=gid, xml_string=xml_string,
1012
                more_data=more_data, media_type=subscriber.getMediaType())
Sebastien Robin's avatar
Sebastien Robin committed
1013 1014
        if not more_data:
          subscriber.removeRemainingObjectPath(object_path)
1015
      else:
Nicolas Delaby's avatar
Nicolas Delaby committed
1016
        result['finished'] = 1
1017
        break
1018 1019 1020 1021 1022
      loop += 1
    result['syncml_data'] = syncml_data
    result['xml_confirmation'] = xml_confirmation
    result['cmd_id'] = cmd_id
    return result
1023

1024 1025
  def applyActionList(self, domain=None, subscriber=None, cmd_id=0,
                      remote_xml=None,conduit=None,simulate=0):
1026 1027 1028
    """
    This just look to a list of action to do, then id applies
    each action one by one, thanks to a conduit
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1029
    """
1030
    xml_confirmation = ''
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1031
    has_next_action = 0
1032 1033
    destination = self.unrestrictedTraverse(domain.getDestinationPath())
    #LOG('applyActionList args',0,'domain : %s\n subscriber : %s\n cmd_id : %s' % (domain, subscriber, cmd_id))
1034
    LOG('applyActionList', 0, self.getSyncActionList(remote_xml))
1035
    for action in self.getSyncActionList(remote_xml):
1036 1037
      if isinstance(action, unicode):
        action = action.encode('utf-8')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1038 1039 1040
      conflict_list = []
      status_code = self.SUCCESS
      # Thirst we have to check the kind of action it is
1041 1042 1043 1044 1045 1046 1047 1048 1049
      partial_data = self.getPartialData(action)
      rid = self.getActionId(action, action.nodeName)
      if action.nodeName != 'Delete':
        if hasattr(conduit, 'getGidFromXML'):
          gid = b16encode(conduit.getGidFromXML(self.getDataText(action)))
        else:
          gid=rid
      else:
        gid=rid
1050
      object_id = domain.generateNewIdWithGenerator(object=destination,gid=gid)
1051
      signature = subscriber.getSignatureFromGid(gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
1052
      if signature is not None and rid != gid:
1053 1054 1055
        #in this case, the object was created on another subscriber than erp5
        # and we should save it's remote id
        signature.setRid(rid)
Nicolas Delaby's avatar
Nicolas Delaby committed
1056
      #LOG('gid == rid ?', 0, 'gid=%s, rid=%s' % (gid, rid))
1057
      object = subscriber.getObjectFromGid(gid)
1058 1059
      if object == None:
        object = subscriber.getObjectFromRid(rid)
1060
      #LOG('applyActionList subscriber.getObjectFromGid %s' % gid,0,object)
Nicolas Delaby's avatar
Nicolas Delaby committed
1061
      if signature is None:
Nicolas Delaby's avatar
Nicolas Delaby committed
1062
        #LOG('applyActionList, signature is None',0,signature)
1063
        if gid == rid:
Nicolas Delaby's avatar
Nicolas Delaby committed
1064
          signature = Signature(id=gid, status=self.NOT_SYNCHRONIZED,
1065 1066 1067 1068 1069
              object=object).__of__(subscriber)
        else:
          signature = Signature(rid=rid, id=gid, status=self.NOT_SYNCHRONIZED,
              object=object).__of__(subscriber)
        signature.setObjectId(object_id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1070 1071
        subscriber.addSignature(signature)
      force = signature.getForce()
Nicolas Delaby's avatar
Nicolas Delaby committed
1072
      #LOG('applyActionList',0,'object: %s' % repr(object))
1073
      if self.checkActionMoreData(action) == 0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1074 1075
        data_subnode = None
        if partial_data != None:
1076 1077 1078 1079 1080
          signature_partial_xml = signature.getPartialXML()
          if signature_partial_xml is not None:
            data_subnode = signature.getPartialXML() + partial_data
          else:
            data_subnode = partial_data
Nicolas Delaby's avatar
Nicolas Delaby committed
1081
          #LOG('SyncModif',0,'data_subnode: %s' % data_subnode)
1082
          if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']:
1083 1084
            data_subnode = Parse(data_subnode)
            data_subnode = data_subnode.childNodes[0] # Because we just created a new xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1085 1086
          # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
        else:
1087
          if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
1088
            data_subnode = self.getDataText(action)
1089
          else:
1090 1091
            data_subnode = self.getDataSubNode(action)
        if action.nodeName == 'Add':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1092
          # Then store the xml of this new subobject
1093
          reset = 0
1094
          if object is None:
1095
            add_data = conduit.addNode(xml=data_subnode, 
1096
                object=destination, object_id=object_id)
1097 1098
            if add_data['conflict_list'] not in ('', None, []):
              conflict_list += add_data['conflict_list']
1099 1100
            # Retrieve directly the object from addNode
            object = add_data['object']
Nicolas Delaby's avatar
Nicolas Delaby committed
1101
            #LOG('XMLSyncUtils, in ADD add_data',0,add_data)
Nicolas Delaby's avatar
Nicolas Delaby committed
1102 1103
            if object is not None:
              signature.setPath(object.getPhysicalPath())
1104
              signature.setObjectId(object.getId())
1105
          else:
1106
            reset = 1
1107 1108 1109
            #Object was retrieve but need to be updated without recreated
            #usefull when an object is only deleted by workflow.
            add_data = conduit.addNode(xml=data_subnode,
1110
                                       object=destination,
1111 1112
                                       object_id=object_id,
                                       sub_object=object)
1113 1114
            if add_data['conflict_list'] not in ('', None, []):
              conflict_list += add_data['conflict_list']
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1115
          if object is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
1116
            #LOG('SyncModif',0,'addNode, found the object')
1117 1118 1119 1120 1121 1122 1123 1124
            if reset:
              #After a reset we want copy the LAST XML view on Signature.
              #this implementation is not sufficient, need to be improved.
              string_io = StringIO()
              PrettyPrint(data_subnode, stream=string_io)
              xml_object = string_io.getvalue()
            else:
              xml_object = domain.getXMLFromObject(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1125
            signature.setStatus(self.SYNCHRONIZED)
1126
            #signature.setId(object.getId())
1127
            signature.setPath(object.getPhysicalPath())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1128
            signature.setXML(xml_object)
1129
            xml_confirmation += self.SyncMLConfirmation(
1130 1131
                cmd_id=cmd_id,
                cmd='Add',
1132 1133
                sync_code=self.ITEM_ADDED,
                remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1134
            cmd_id +=1
1135
        elif action.nodeName == 'Replace':
Nicolas Delaby's avatar
Nicolas Delaby committed
1136
          #LOG('SyncModif',0,'object: %s will be updated...' % str(object))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1137
          if object is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
1138
            #LOG('SyncModif',0,'object: %s will be updated...' % object.id)
1139
            signature = subscriber.getSignatureFromGid(gid)
1140 1141
            if signature is None:
              signature = subscriber.getSignatureFromRid(gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
1142
            #LOG('SyncModif',0,'previous signature: %s' % str(signature))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1143
            previous_xml = signature.getXML()
1144
            #LOG('SyncModif',0,'previous signature: %i' % len(previous_xml))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1145
            conflict_list += conduit.updateNode(xml=data_subnode, object=object,
1146 1147
                              previous_xml=signature.getXML(),force=force,
                              simulate=simulate)
1148
            xml_object = domain.getXMLFromObject(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1149 1150 1151 1152
            signature.setTempXML(xml_object)
            if conflict_list != []:
              status_code = self.CONFLICT
              signature.setStatus(self.CONFLICT)
1153 1154
              signature.setConflictList(signature.getConflictList() \
                  + conflict_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1155 1156 1157 1158
              string_io = StringIO()
              PrettyPrint(data_subnode,stream=string_io)
              data_subnode_string = string_io.getvalue()
              signature.setPartialXML(data_subnode_string)
1159
            elif not simulate:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1160
              signature.setStatus(self.SYNCHRONIZED)
1161 1162 1163 1164 1165
            xml_confirmation += self.SyncMLConfirmation(\
                cmd_id=cmd_id, 
                cmd='Replace', 
                sync_code=status_code,
                remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1166
            cmd_id +=1
1167
            if simulate:
Sebastien Robin's avatar
Sebastien Robin committed
1168
              # This means we are on the publisher side and we want to store
1169 1170 1171 1172 1173
              # the xupdate from the subscriber and we also want to generate
              # the current xupdate from the last synchronization
              string_io = StringIO()
              PrettyPrint(data_subnode,stream=string_io)
              data_subnode_string = string_io.getvalue()
Nicolas Delaby's avatar
Nicolas Delaby committed
1174
              #LOG('applyActionList, subscriber_xupdate:',0,data_subnode_string)
1175 1176
              signature.setSubscriberXupdate(data_subnode_string)

1177
        elif action.nodeName == 'Delete':
1178
          object_id = signature.getId()
Nicolas Delaby's avatar
Nicolas Delaby committed
1179
          #LOG('Delete on : ',0, (signature.getId(), subscriber.getObjectFromGid(object_id)))
1180
          if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']: 
1181
            data_subnode = self.getDataText(action)
1182
          else:
1183 1184 1185
            data_subnode = self.getDataSubNode(action)
          if subscriber.getObjectFromGid(object_id) not in (None, ''):
          #if the object exist:
1186
            conduit.deleteNode(xml=data_subnode, object=destination, 
1187 1188 1189 1190 1191 1192 1193
                object_id=subscriber.getObjectFromGid(object_id).getId())
            subscriber.delSignature(gid)
          xml_confirmation += self.SyncMLConfirmation(
              cmd_id=cmd_id, 
              cmd='Delete',
              sync_code=status_code,
              remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1194 1195 1196 1197
      else: # We want to retrieve more data
        signature.setStatus(self.PARTIAL)
        #LOG('SyncModif',0,'setPartialXML: %s' % str(previous_partial))
        previous_partial = signature.getPartialXML() or ''
1198 1199
        #if previous_partial.find(partial_data)<0: # XXX bad thing
        previous_partial += partial_data
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1200
        signature.setPartialXML(previous_partial)
1201
        #LOG('SyncModif',0,'previous_partial: %s' % str(previous_partial))
Nicolas Delaby's avatar
Nicolas Delaby committed
1202
        #LOG('SyncModif',0,'waiting more data for :%s' % signature.getId())
1203 1204 1205 1206 1207 1208 1209
        #xml_confirmation += self.SyncMLConfirmation(cmd_id, object_gid, 
        #    self.WAITING_DATA, action.nodeName)
        xml_confirmation += self.SyncMLConfirmation(\
            cmd_id=cmd_id, 
            cmd=action.nodeName, 
            sync_code=self.WAITING_DATA,
            remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1210 1211 1212
      if conflict_list != [] and signature is not None:
        # We had a conflict
        signature.setStatus(self.CONFLICT)
1213

1214 1215 1216 1217 1218 1219 1220
    return (xml_confirmation,has_next_action,cmd_id)

  def applyStatusList(self, subscriber=None,remote_xml=None):
    """
    This read a list of status list (ie syncml confirmations).
    This method have to change status codes on signatures
    """
1221
    status_list = self.getSyncBodyStatusList(remote_xml)
1222 1223
    has_status_list = 0
    destination_waiting_more_data = 0
1224 1225 1226 1227 1228 1229 1230
    if status_list != []:
      for status in status_list:
        status_cmd = status['cmd']
        #if status_cmd in ('Delete'):
        #  object_gid = status['target']
        #else:
        object_gid = status['source']
1231 1232
        if object_gid in ('', None, []):
          object_gid = status['target']
1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255
        status_code = int(status['code'])
        if status_cmd in ('Add','Replace'):
          has_status_list = 1
          signature = subscriber.getSignatureFromGid(object_gid)
          if signature == None:
            signature = subscriber.getSignatureFromRid(object_gid)
          if status_code == self.CHUNK_OK:
            destination_waiting_more_data = 1
            signature.setStatus(self.PARTIAL)
          elif status_code == self.CONFLICT:
            signature.setStatus(self.CONFLICT)
          elif status_code == self.CONFLICT_MERGE:
            # We will have to apply the update, and we should not care 
            # about conflicts, so we have to force the update
            signature.setStatus(self.NOT_SYNCHRONIZED)
            signature.setForce(1)
          elif status_code == self.CONFLICT_CLIENT_WIN:
            # The server was agree to apply our updates, nothing to do
            signature.setStatus(self.SYNCHRONIZED)
          elif status_code in (self.SUCCESS, self.ITEM_ADDED):
            signature.setStatus(self.SYNCHRONIZED)
        elif status_cmd == 'Delete':
          if status_code == self.SUCCESS:
1256 1257
            signature = subscriber.getSignatureFromGid(object_gid) or \
            subscriber.getSignatureFromRid(object_gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
1258 1259
            if signature is not None:
              subscriber.delSignature(signature.getGid())
1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277
    return (destination_waiting_more_data, has_status_list)


class XMLSyncUtils(XMLSyncUtilsMixin):

  def Sync(self, id, msg=None, RESPONSE=None):
    """
    This is the main method for synchronization
    """
    pass

  def SyncInit(self, domain):
    """
    Initialization of a synchronization, this is
    used for the first message of every synchronization
    """
    pass

1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296
  def getConduitByName(self, conduit_name):
    """
    Get Conduit Object by given name.
    The Conduit can be located in Any Products according to naming Convention
    Products.<Product Name>.Conduit.<Conduit Module> ,if conduit_name equal module's name.
    By default Conduit must be defined in Products.ERP5SyncML.Conduit.<Conduit Module>
    """
    from Products.ERP5SyncML import Conduit
    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)()
    return conduit

1297 1298 1299 1300 1301 1302 1303 1304 1305
  def SyncModif(self, domain, remote_xml):
    """
    Modification Message, this is used after the first
    message in order to send modifications.
    """
    """
      Send the server modification, this happens after the Synchronization
      initialization
    """
1306
    has_response = 0 #check if syncmodif replies to this messages
1307
    cmd_id = 1 # specifies a SyncML message-unique command identifier
1308
    LOG('SyncModif',0,'Starting... domain: %s' % str(domain))
1309

Sebastien Robin's avatar
Sebastien Robin committed
1310
    first_node = remote_xml.childNodes[0]
1311 1312 1313
    # Get informations from the header
    xml_header = first_node.childNodes[1]
    if xml_header.nodeName != "SyncHdr":
1314
      LOG('PubSyncModif',0,'This is not a SyncML Header')
Sebastien Robin's avatar
Sebastien Robin committed
1315
      raise ValueError, "Sorry, This is not a SyncML Header"
1316 1317

    subscriber = domain # If we are the client, this is fine
1318
    simulate = 0 # used by applyActionList, should be 0 for client
1319
    if domain.domain_type == self.PUB:
1320
      simulate = 1
1321
      subscription_url = self.getSubscriptionUrl(xml_header) 
1322
      subscriber = domain.getSubscriber(subscription_url)
1323

1324 1325
    # We have to check if this message was not already, this can be dangerous
    # to update two times the same object
Sebastien Robin's avatar
Sebastien Robin committed
1326 1327 1328
    message_id = self.getMessageId(remote_xml)
    correct_message = subscriber.checkCorrectRemoteMessageId(message_id)
    if not correct_message: # We need to send again the message
1329
      LOG('SyncModif, no correct message:',0,"sending again...")
1330
      last_xml = subscriber.getLastSentMessage()
1331
      LOG("last_xml :", 0, last_xml)
1332 1333 1334
      if last_xml != '':
        has_response = 1
        if domain.domain_type == self.PUB: # We always reply
1335 1336
          self.sendResponse(from_url=domain.publication_url, 
              to_url=subscriber.subscription_url, sync_id=domain.getTitle(), 
1337 1338
              xml=last_xml,domain=domain, 
              content_type=domain.getSyncContentType()) 
1339
        elif domain.domain_type == self.SUB:
1340
          self.sendResponse(from_url=domain.subscription_url, 
1341
              to_url=domain.publication_url, sync_id=domain.getTitle(), 
1342 1343
              xml=last_xml, domain=domain,
              content_type=domain.getSyncContentType())
1344 1345 1346
      return {'has_response':has_response,'xml':last_xml}
    subscriber.setLastSentMessage('')

1347 1348 1349 1350 1351 1352
    # First apply the list of status codes
    (destination_waiting_more_data,has_status_list) = self.applyStatusList(
                                         subscriber=subscriber,
                                         remote_xml=remote_xml)

    alert_code = self.getAlertCode(remote_xml)
Kevin Deldycke's avatar
Kevin Deldycke committed
1353
    # Import the conduit and get it
1354
    conduit = self.getConduitByName(subscriber.getConduit())
1355
    # Then apply the list of actions
1356 1357 1358 1359 1360 1361
    (xml_confirmation, has_next_action, cmd_id) = self.applyActionList(
                                      cmd_id=cmd_id,
                                      domain=domain,
                                      subscriber=subscriber,
                                      remote_xml=remote_xml,
                                      conduit=conduit, simulate=simulate)
1362
    #LOG('SyncModif, has_next_action:',0,has_next_action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1363

Sebastien Robin's avatar
Sebastien Robin committed
1364 1365 1366
    xml_list = []
    xml = xml_list.append
    xml('<SyncML>\n')
1367

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1368 1369
    # syncml header
    if domain.domain_type == self.PUB:
1370 1371 1372
      xml(self.SyncMLHeader(subscriber.getSessionId(), 
        subscriber.incrementMessageId(), subscriber.getSubscriptionUrl(), 
        domain.getPublicationUrl()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1373
    elif domain.domain_type == self.SUB:
Sebastien Robin's avatar
Sebastien Robin committed
1374 1375
      xml(self.SyncMLHeader(domain.getSessionId(), domain.incrementMessageId(),
        domain.getPublicationUrl(), domain.getSubscriptionUrl()))
1376
    
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1377 1378 1379 1380
    # Add or replace objects
    syncml_data = ''

    # syncml body
Sebastien Robin's avatar
Sebastien Robin committed
1381
    xml(' <SyncBody>\n')
1382

1383 1384 1385
    xml_status, cmd_id = self.SyncMLStatus(remote_xml, self.SUCCESS, cmd_id, 
         subscriber.getNextAnchor(), subscription=subscriber).values()
    xml(xml_status)
1386

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1387 1388 1389
    destination_url = ''
    # alert message if we want more data
    if destination_waiting_more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
1390
      xml(self.SyncMLAlert(cmd_id, self.WAITING_DATA,
1391 1392
                              subscriber.getTargetURI(),
                              subscriber.getSourceURI(),
1393
                              subscriber.getLastAnchor(),
Sebastien Robin's avatar
Sebastien Robin committed
1394
                              subscriber.getNextAnchor()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1395
    # Now we should send confirmations
1396
    cmd_id_before_getsyncmldata = cmd_id
1397
    cmd_id = cmd_id+1
1398
    if domain.getActivityEnabled():
1399 1400 1401 1402 1403
      #use activities to get SyncML data.
      if not (isinstance(remote_xml, str) or isinstance(remote_xml, unicode)):
        string_io = StringIO()
        PrettyPrint(remote_xml,stream=string_io)
        remote_xml = string_io.getvalue()
Sebastien Robin's avatar
Sebastien Robin committed
1404
      self.activate(activity='SQLQueue').SyncModifActivity(
1405 1406 1407 1408 1409 1410 1411 1412 1413 1414
                      domain_relative_url = domain.getRelativeUrl(),
                      remote_xml = remote_xml,
                      subscriber_relative_url = subscriber.getRelativeUrl(),
                      cmd_id = cmd_id,
                      xml_confirmation = xml_confirmation,
                      syncml_data = '',
                      cmd_id_before_getsyncmldata = cmd_id_before_getsyncmldata,
                      xml_list = xml_list,
                      has_status_list = has_status_list,
                      has_response = has_response )
Nicolas Delaby's avatar
Nicolas Delaby committed
1415
      return {'has_response':1, 'xml':''}
1416 1417
    else:
      result = self.getSyncMLData(domain=domain,
1418 1419
                             remote_xml=remote_xml,
                             subscriber=subscriber,
1420
                             cmd_id=cmd_id,xml_confirmation=xml_confirmation,
1421 1422
                             conduit=conduit,
                             max=self.MAX_OBJECTS)
1423 1424 1425 1426 1427
      syncml_data = result['syncml_data']
      xml_confirmation = result['xml_confirmation']
      cmd_id = result['cmd_id']
      return self.sendSyncModif(syncml_data, cmd_id_before_getsyncmldata,
                                subscriber, domain, xml_confirmation,
1428 1429
                                remote_xml, xml_list, has_status_list, 
                                has_response)
1430 1431 1432 1433 1434 1435

  def SyncModifActivity(self, **kw):
    domain = self.unrestrictedTraverse(kw['domain_relative_url'])
    subscriber = self.unrestrictedTraverse(kw['subscriber_relative_url'])
    conduit = subscriber.getConduit()
    result = self.getSyncMLData(domain = domain, subscriber = subscriber,
1436
                                conduit = conduit, max = self.MAX_OBJECTS, **kw)
1437
    syncml_data = result['syncml_data']
1438
    cmd_id = result['cmd_id']
1439
    kw['syncml_data'] = syncml_data
1440
    kw['cmd_id'] = cmd_id
1441
    finished = result['finished']
1442
    #LOG('finished =',0,finished)
1443
    if not finished:
Sebastien Robin's avatar
Sebastien Robin committed
1444
      self.activate(activity='SQLQueue').SyncModifActivity(**kw)
1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460
    else:
      xml_confirmation = result['xml_confirmation']
      cmd_id = result['cmd_id']
      cmd_id_before_getsyncmldata = kw['cmd_id_before_getsyncmldata']
      remote_xml = Parse(kw['remote_xml'])
      xml_list = kw['xml_list']
      has_status_list = kw['has_status_list']
      has_response = kw['has_response']
      return self.sendSyncModif(syncml_data, cmd_id_before_getsyncmldata,
                              subscriber, domain, xml_confirmation,
                              remote_xml, xml_list, has_status_list, has_response)

  def sendSyncModif(self, syncml_data, cmd_id_before_getsyncmldata, subscriber,
                    domain, xml_confirmation, remote_xml, xml_list,
                    has_status_list, has_response):
    xml = xml_list.append
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1461
    if syncml_data != '':
Sebastien Robin's avatar
Sebastien Robin committed
1462
      xml('  <Sync>\n')
1463
      xml('   <CmdID>%s</CmdID>\n' % cmd_id_before_getsyncmldata)
1464 1465 1466 1467 1468 1469 1470 1471
      if subscriber.getTargetURI() not in ('', None):
        xml('   <Target>\n')
        xml('    <LocURI>%s</LocURI>\n' % subscriber.getTargetURI())
        xml('   </Target>\n')
      if subscriber.getSourceURI() not in ('', None):
        xml('   <Source>\n')
        xml('    <LocURI>%s</LocURI>\n' % subscriber.getSourceURI())
        xml('   </Source>\n')
Sebastien Robin's avatar
Sebastien Robin committed
1472 1473
      xml(syncml_data)
      xml('  </Sync>\n')
1474
    xml(xml_confirmation)
Sebastien Robin's avatar
Sebastien Robin committed
1475 1476 1477 1478
    xml('  <Final/>\n')
    xml(' </SyncBody>\n')
    xml('</SyncML>\n')
    xml_a = ''.join(xml_list)
1479

1480
    if domain.domain_type == self.PUB: # We always reply
Sebastien Robin's avatar
Sebastien Robin committed
1481 1482 1483
      subscriber.setLastSentMessage(xml_a)
      self.sendResponse(from_url=domain.publication_url, 
          to_url=subscriber.subscription_url, sync_id=domain.getTitle(), 
1484 1485
          xml=xml_a,domain=domain,
          content_type=domain.getSyncContentType())
1486 1487 1488
      has_response = 1
    elif domain.domain_type == self.SUB:
      if self.checkAlert(remote_xml) or \
1489
          (xml_confirmation,syncml_data)!=('','') or \
1490
          has_status_list:
Sebastien Robin's avatar
Sebastien Robin committed
1491 1492 1493
        subscriber.setLastSentMessage(xml_a)
        self.sendResponse(from_url=domain.subscription_url, 
            to_url=domain.publication_url, sync_id=domain.getTitle(), 
1494 1495
            xml=xml_a,domain=domain,
            content_type=domain.getSyncContentType())
1496
        has_response = 1
Sebastien Robin's avatar
Sebastien Robin committed
1497
    return {'has_response':has_response,'xml':xml_a}
1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527

  def xml2wbxml(self, xml):
    """
    convert xml string to wbxml using a temporary file
    """
    LOG('xml2wbxml starting ...',0,'')
    import os
    f = open('/tmp/xml2wbxml', 'w')
    f.write(xml)
    f.close()
    os.system('/usr/bin/xml2wbxml -o /tmp/xml2wbxml /tmp/xml2wbxml')
    f = open('/tmp/xml2wbxml', 'r')
    wbxml = f.read()
    f.close()
    return wbxml

  def wbxml2xml(self, wbxml):
    """
    convert wbxml string to xml using a temporary file
    """
    LOG('wbxml2xml starting ...',0,'')
    import os
    f = open('/tmp/wbxml2xml', 'w')
    f.write(wbxml)
    f.close()
    os.system('/usr/bin/wbxml2xml -o /tmp/wbxml2xml /tmp/wbxml2xml')
    f = open('/tmp/wbxml2xml', 'r')
    xml = f.read()
    f.close()
    return xml