XMLSyncUtils.py 44.1 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
##############################################################################
#
# 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
from xml.dom.ext.reader.Sax2 import FromXml
Sebastien Robin's avatar
Sebastien Robin committed
33
from xml.dom.minidom import parse, parseString
34
from DateTime import DateTime
Jean-Paul Smets's avatar
Jean-Paul Smets committed
35 36
from cStringIO import StringIO
from xml.dom.ext import PrettyPrint
37
import random
38 39 40 41 42 43
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
44 45 46
import commands
from zLOG import LOG

47
class XMLSyncUtilsMixin(SyncCode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134

  def SyncMLHeader(self, session_id, msg_id, target, source):
    """
      Since the Header is always almost the same, this is the
      way to set one quickly.
    """
    xml = ""
    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>%s</Target>\n' % target
    xml += '  <Source>%s</Source>\n' % source
    xml += ' </SyncHdr>\n'
    return xml

  def SyncMLAlert(self, cmd_id, sync_code, target, source, last_anchor, next_anchor):
    """
      Since the Alert section is always almost the same, this is the
      way to set one quickly.
    """
    xml = ""
    xml += '  <Alert>\n'
    xml += '   <CmdID>%s</CmdID>\n' % cmd_id
    xml += '   <Data>%s</Data>\n' % sync_code
    xml += '   <Item>\n'
    xml += '    <Target>%s</Target>\n' % target
    xml += '    <Source>%s</Source>\n' % source
    xml += '    <Meta>\n'
    xml += '     <Anchor xmlns=\'syncml:metinf\'>\n'
    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'
    return xml

  def SyncMLStatus(self, cmd_id, target_ref, source_ref, sync_code, next_anchor):
    """
      Since the Status section is always almost the same, this is the
      way to set one quickly.
    """
    xml = ""
    xml += '  <Status>\n'
    xml += '   <CmdID>%s</CmdID>\n' % cmd_id
    xml += '   <TargetRef>%s</TargetRef>\n' % target_ref
    xml += '   <SourceRef>%s</SourceRef>\n' % source_ref
    xml += '   <Data>%s</Data>\n' % sync_code
    xml += '   <Item>\n'
    xml += '    <Data>\n'
    xml += '     <Anchor xmlns=\'syncml:metinf\'>\n'
    xml += '      <Next>%s</Next>\n' % next_anchor
    xml += '     </Anchor>\n'
    xml += '    </Data>\n'
    xml += '   </Item>\n'
    xml += '  </Status>\n'
    return xml

  def SyncMLConfirmation(self, cmd_id, target_ref, sync_code, cmd):
    """
    This is used in order ton confirm that an object was correctly
    synchronized
    """
    xml = ""
    xml += '  <Status>\n'
    xml += '   <CmdID>%s</CmdID>\n' % cmd_id
    xml += '   <TargetRef>%s</TargetRef>\n' % target_ref
    xml += '   <Cmd>%s</Cmd>' % cmd
    xml += '   <Data>%s</Data>\n' % sync_code
    xml += '  </Status>\n'
    return xml

  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
    LOG('SubSendMail',0,'from: %s, to: %s' % (fromaddr,toaddr))
    server = smtplib.SMTP('localhost')
    server.sendmail(fromaddr, toaddr, msg)
    # if we want to send the email to someone else (debugging)
135
    #server.sendmail(fromaddr, "seb@localhost", msg)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
136 137
    server.quit()

138 139
  def addXMLObject(self, cmd_id=0, object=None, xml_string=None,
                   more_data=0,gid=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
140 141 142 143 144 145 146 147
    """
      Add an object with the SyncML protocol
    """
    xml = ""
    xml += '   <Add>\n'
    xml += '    <CmdID>%s</CmdID>\n' % cmd_id
    xml += '    <Meta><Type>%s</Type></Meta>\n' % object.portal_type
    xml += '    <Item>\n'
148
    xml += '     <Source><LocURI>%s</LocURI></Source>\n' % gid
Jean-Paul Smets's avatar
Jean-Paul Smets committed
149 150 151 152 153 154 155 156 157
    xml += '     <Data>\n'
    xml += xml_string
    xml += '     </Data>\n'
    if more_data == 1:
      xml += '     <MoreData/>\n'
    xml += '    </Item>\n'
    xml += '   </Add>\n'
    return xml

158
  def deleteXMLObject(self, cmd_id=0, object_gid=None, xml_object=''):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
159 160 161 162 163 164 165
    """
      Add an object with the SyncML protocol
    """
    xml = ""
    xml += '   <Delete>\n'
    xml += '    <CmdID>%s</CmdID>\n' % cmd_id
    xml += '    <Item>\n'
166
    xml += '     <Source><LocURI>%s</LocURI></Source>\n' % object_gid
Jean-Paul Smets's avatar
Jean-Paul Smets committed
167 168 169 170 171 172
    xml += '     <Data>\n'
    xml += '     </Data>\n'
    xml += '    </Item>\n'
    xml += '   </Delete>\n'
    return xml

173 174
  def replaceXMLObject(self, cmd_id=0, object=None, xml_string=None,
                       more_data=0,gid=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
175 176 177 178 179 180 181 182
    """
      Add an object with the SyncML protocol
    """
    xml = ""
    xml += '   <Replace>\n'
    xml += '    <CmdID>%s</CmdID>\n' % cmd_id
    xml += '    <Meta><Type>%s</Type></Meta>\n' % object.portal_type
    xml += '    <Item>\n'
183
    xml += '     <Source><LocURI>%s</LocURI></Source>\n' % str(gid)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
    xml += '     <Data>\n'
    xml += xml_string
    xml += '     </Data>\n'
    if more_data == 1:
      xml += '     <MoreData/>\n'
    xml += '    </Item>\n'
    xml += '   </Replace>\n'
    return xml

  def getXupdateObject(self, object=None, xml_mapping=None, old_xml=None):
    """
    Generate the xupdate with the new object and the old xml
    We have to use xmldiff as a command line tool, because all
    over the xmldiff code, there's some print to the standard
    output, so this is unusable
    """
200 201 202 203
    filename = str(random.randrange(1,2147483600))
    old_filename = filename + '.old'
    new_filename = filename + '.new'
    file1 = open('/tmp/%s' % new_filename,'w')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
204 205
    file1.write(self.getXMLObject(object=object,xml_mapping=xml_mapping))
    file1.close()
206
    file2 = open('/tmp/%s'% old_filename,'w')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
207 208
    file2.write(old_xml)
    file2.close()
209
    xupdate = commands.getoutput('erp5diff /tmp/%s /tmp/%s' % (old_filename,new_filename))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
210
    xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
211 212
    commands.getstatusoutput('rm -f /tmp/%s' % old_filename)
    commands.getstatusoutput('rm -f /tmp/%s' % new_filename)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
213 214 215 216 217 218 219 220
    return xupdate

  def getXMLObject(self, object=None, xml_mapping=None):
    """
    This just allow to get the xml of the object
    """
    xml_method = None
    xml = ""
221 222 223 224 225 226 227
    if xml_mapping is not None:
      if hasattr(object,xml_mapping):
        xml_method = getattr(object,xml_mapping)
      elif hasattr(object,'manage_FTPget'):
        xml_method = getattr(object,'manage_FTPget')
      if xml_method is not None:
        xml = xml_method()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
228 229
    return xml

230 231 232 233 234 235 236 237 238 239 240 241 242 243
  def getSessionId(self, xml):
    """
    We will retrieve the session id of the message
    """
    session_id = 0
    for subnode in self.getElementNodeList(xml):
      if subnode.nodeName == 'SyncML':
        for subnode1 in self.getElementNodeList(subnode):
          if subnode1.nodeName == 'SyncHdr':
            for subnode2 in self.getElementNodeList(subnode1):
              if subnode2.nodeName == 'SessionID':
                session_id = int(subnode2.childNodes[0].data)
    return session_id

Jean-Paul Smets's avatar
Jean-Paul Smets committed
244 245 246 247 248
  def getAlertLastAnchor(self, xml_stream):
    """
      Return the value of the last anchor, in the
      alert section of the xml_stream
    """
Sebastien Robin's avatar
Sebastien Robin committed
249
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
250 251 252 253 254 255 256 257 258

    # Get informations from the body
    client_body = first_node.childNodes[3]
    for subnode in client_body.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Alert":
        for subnode2 in subnode.childNodes:
          if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == "Item":
            for subnode3 in subnode2.childNodes:
              if subnode3.nodeType == subnode3.ELEMENT_NODE and subnode3.nodeName == "Meta":
Sebastien Robin's avatar
Sebastien Robin committed
259
               for subnode4 in subnode3.childNodes:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
260 261 262
                  if subnode4.nodeType == subnode4.ELEMENT_NODE and subnode4.nodeName == "Anchor":
                    for subnode5 in subnode4.childNodes:
                      # Get the last time we had a synchronization
Sebastien Robin's avatar
Sebastien Robin committed
263
                     if subnode5.nodeType == subnode5.ELEMENT_NODE and subnode5.nodeName == "Last":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
264 265 266 267 268 269 270 271
                        last_anchor = subnode5.childNodes[0].data
                        return last_anchor

  def getAlertNextAnchor(self, xml_stream):
    """
      Return the value of the next anchor, in the
      alert section of the xml_stream
    """
Sebastien Robin's avatar
Sebastien Robin committed
272
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
273 274 275 276 277 278 279 280 281 282 283 284 285 286
    if first_node.nodeName != "SyncML":
      print "This is not a SyncML message"

    # Get informations from the body
    client_body = first_node.childNodes[3]
    if client_body.nodeName != "SyncBody":
      print "This is not a SyncML Body"
    for subnode in client_body.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Alert":
        for subnode2 in subnode.childNodes:
          if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == "Item":
            for subnode3 in subnode2.childNodes:
              if subnode3.nodeType == subnode3.ELEMENT_NODE and subnode3.nodeName == "Meta":
                for subnode4 in subnode3.childNodes:
Sebastien Robin's avatar
Sebastien Robin committed
287
                 if subnode4.nodeType == subnode4.ELEMENT_NODE and subnode4.nodeName == "Anchor":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
                    for subnode5 in subnode4.childNodes:
                      # Get the last time we had a synchronization
                      if subnode5.nodeType == subnode5.ELEMENT_NODE and subnode5.nodeName == "Next":
                        next_anchor = subnode5.childNodes[0].data
                        return next_anchor

  def getStatusTarget(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
    # Get informations from the body
    if xml.nodeName=='Status':
      for subnode in xml.childNodes:
        if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'TargetRef':
          return subnode.childNodes[0].data
    return None

  def getStatusCode(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
    # Get informations from the body
    if xml.nodeName=='Status':
      for subnode in xml.childNodes:
        if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'Data':
          return int(subnode.childNodes[0].data)
    return None

316 317 318 319 320 321 322 323 324 325 326
  def getStatusCommand(self, xml):
    """
      Return the value of the command inside the xml_stream
    """
    # Get informations from the body
    if xml.nodeName=='Status':
      for subnode in xml.childNodes:
        if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'Cmd':
          return subnode.childNodes[0].data
    return None

Jean-Paul Smets's avatar
Jean-Paul Smets committed
327 328 329 330 331
  def getAlertCode(self, xml_stream):
    """
      Return the value of the alert code inside the full syncml message
    """
    # Get informations from the body
Sebastien Robin's avatar
Sebastien Robin committed
332
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
    client_body = first_node.childNodes[3]
    if client_body.nodeName != "SyncBody":
      LOG('XMLSyncUtils.getAlertCode',0,"This is not a SyncML Body")
    alert = 0
    for subnode in client_body.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName=='Alert':
        for subnode1 in subnode.childNodes:
          if subnode1.nodeType == subnode1.ELEMENT_NODE and subnode1.nodeName == 'Data':
            return int(subnode1.childNodes[0].data)
    return None

  def checkAlert(self, xml_stream):
    """
      Check if there's an Alert section in the xml_xtream
    """
Sebastien Robin's avatar
Sebastien Robin committed
348
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
349 350 351 352 353 354 355 356 357 358 359 360 361
    client_body = first_node.childNodes[3]
    if client_body.nodeName != "SyncBody":
      print "This is not a SyncML Body"
    alert = 0
    for subnode in client_body.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Alert":
        alert = 1
    return alert

  def checkSync(self, xml_stream):
    """
      Check if there's an Sync section in the xml_xtream
    """
Sebastien Robin's avatar
Sebastien Robin committed
362
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
363 364 365 366 367 368 369 370 371 372 373 374
    client_body = first_node.childNodes[3]
    if client_body.nodeName != "SyncBody":
      LOG('checkSync',0,"This is not a SyncML Body")
    for subnode in client_body.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Sync":
        return 1
    return 0

  def CheckStatus(self, xml_stream):
    """
      Check if there's a Status section in the xml_xtream
    """
Sebastien Robin's avatar
Sebastien Robin committed
375
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
    client_body = first_node.childNodes[3]
    if client_body.nodeName != "SyncBody":
      print "This is not a SyncML Body"
    status = None
    for subnode in client_body.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Status":
        status = "1"
    return status

  def getNextSyncAction(self, xml_stream, last_action):
    """
      It goes throw actions in the Sync section of the SyncML file,
      then it returns the next action (could be "add", "replace",
      "delete").
    """
Sebastien Robin's avatar
Sebastien Robin committed
391
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
    client_body = first_node.childNodes[3]
    if client_body.nodeName != "SyncBody":
      print "This is not a SyncML Body"
    next_action = None
    for subnode in client_body.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Sync":
        # if we didn't use this method before
        if last_action == None and len(subnode.childNodes) > 1:
          next_action = subnode.childNodes[1]
        else:
          found = None
          for subnode2 in subnode.childNodes:
            if subnode2.nodeType == subnode.ELEMENT_NODE and subnode2 != last_action and found is None:
              pass
            elif subnode2.nodeType == subnode.ELEMENT_NODE and subnode2 == last_action and found is None:
              found = 1
            elif subnode2.nodeType == subnode.ELEMENT_NODE and found is not None:
              next_action = subnode2
              break
    return next_action

  def getNextSyncBodyStatus(self, xml_stream, last_status):
    """
      It goes throw actions in the Sync section of the SyncML file,
      then it returns the next action (could be "add", "replace",
      "delete").
    """
Sebastien Robin's avatar
Sebastien Robin committed
419
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
    client_body = first_node.childNodes[3]
    if client_body.nodeName != "SyncBody":
      LOG('getNextSyncBodyStatus',0,"This is not a SyncML Body")
    next_status = None
    found = None
    for subnode in client_body.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Status":
        # if we didn't use this method before
        if last_status == None:
          next_status = subnode
          return next_status
        elif subnode == last_status and found is None:
          found = 1
        elif found is not None:
          return subnode
    return next_status

  def getDataSubNode(self, action):
    """
      Return the node starting with <object....> of the action
    """
    for subnode in action.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'Item':
        for subnode2 in subnode.childNodes:
          if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == 'Data':
            for subnode3 in subnode2.childNodes:
              #if subnode3.nodeType == subnode3.ELEMENT_NODE and subnode3.nodeName == 'object':
              if subnode3.nodeType == subnode3.ELEMENT_NODE:
                return subnode3

  def getPartialData(self, action):
    """
      Return the node starting with <object....> of the action
    """
    for subnode in action.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'Item':
        for subnode2 in subnode.childNodes:
          if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == 'Data':
            for subnode3 in subnode2.childNodes:
              #if subnode3.nodeType == subnode3.ELEMENT_NODE and subnode3.nodeName == 'object':
              if subnode3.nodeType == subnode3.COMMENT_NODE:
                # No need to remove comment, it is already done by FromXml
                #if subnode3.data.find('<!--')>=0:
                #  data = subnode3.data
                #  data = data[data.find('<!--')+4:data.rfind('-->')]
465 466 467 468
                xml = subnode3.data
                if type(xml) is type(u'a'):
                  xml = xml.encode('utf-8')
                return xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515

    return None


  def getAttributeNodeList(self, node):
    """
      Return attributesNodes that are ElementNode XXX may be not needed at all
    """
    subnode_list = []
    for subnode in node.attributes:
      if subnode.nodeType == subnode.ATTRIBUTE_NODE:
        subnode_list += [subnode]
    return subnode_list

  def getActionId(self, action):
    """
      Return the rid of the object described by the action
    """
    for subnode in action.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'Item':
        for subnode2 in subnode.childNodes:
          if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == 'Source':
            for subnode3 in subnode2.childNodes:
              if subnode3.nodeType == subnode3.ELEMENT_NODE and subnode3.nodeName == 'LocURI':
                return str(subnode3.childNodes[0].data)

  def checkActionMoreData(self, action):
    """
      Return the rid of the object described by the action
    """
    for subnode in action.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'Item':
        for subnode2 in subnode.childNodes:
          if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == 'MoreData':
            return 1
    return 0

  def getActionType(self, action):
    """
      Return the type of the object described by the action
    """
    for subnode in action.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == 'Meta':
        for subnode2 in subnode.childNodes:
          if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == 'Type':
            return str(subnode2.childNodes[0].data)

516 517 518 519
  def getElementNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
Sebastien Robin's avatar
Sebastien Robin committed
520
    #return node.getElementsByTagName('*')
521
    subnode_list = []
522
    for subnode in node.childNodes or []:
523 524 525 526
      if subnode.nodeType == subnode.ELEMENT_NODE:
        subnode_list += [subnode]
    return subnode_list

527 528 529 530 531
  def getTextNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
    subnode_list = []
532
    for subnode in node.childNodes or []:
533 534 535 536
      if subnode.nodeType == subnode.TEXT_NODE:
        subnode_list += [subnode]
    return subnode_list

537 538 539 540 541
  def getAttributeNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
    attribute_list = []
Sebastien Robin's avatar
Sebastien Robin committed
542
    for subnode in node.attributes.values() or []:
543 544 545 546
      if subnode.nodeType == subnode.ATTRIBUTE_NODE:
        attribute_list += [subnode]
    return attribute_list

547
  def getSyncMLData(self, domain=None,remote_xml=None,cmd_id=0,
548
                          subscriber=None,destination_path=None,
549
                          xml_confirmation=None,conduit=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
550
    """
551 552
    This generate the syncml data message. This returns a string
    with all modification made locally (ie replace, add ,delete...)
553 554 555

    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
556
    """
557 558
    local_gid_list = []
    syncml_data = ''
559

560
    if subscriber.getRemainingObjectIdList() is None:
561
      object_list = domain.getObjectList()
562 563 564
      object_id_list = map(lambda x: x.id,object_list)
      LOG('getSyncMLData, object_id_list',0,object_id_list)
      subscriber.setRemainingObjectIdList(object_id_list)
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583

      #object_gid = domain.getGidFromObject(object)
      local_gid_list = map(lambda x: domain.getGidFromObject(x),object_list)
      # Objects to remove
      #for object_id in id_list:
      for object_gid in subscriber.getGidList():
        if not (object_gid in local_gid_list):
          # This is an object to remove
          signature = subscriber.getSignature(object_gid)
          if signature.getStatus()!=self.PARTIAL: # If partial, then we have a signature
                                                  # but no local object
            xml_object = signature.getXML()
            if xml_object is not None: # This prevent to delete an object that we
                                      # were not able to create
              syncml_data += self.deleteXMLObject(xml_object=signature.getXML() or '',
                                                  object_gid=object_gid,cmd_id=cmd_id)


    #for object in domain.getObjectList():
584 585
    for object_id in subscriber.getRemainingObjectIdList():
      object = subscriber.getDestination()._getOb(object_id)
586
      status = self.SENT
587 588 589 590 591
      #gid_generator = getattr(object,domain.getGidGenerator(),None)
      object_gid = domain.getGidFromObject(object)
      local_gid_list += [object_gid]
      #if gid_generator is not None:
      #  object_gid = gid_generator()
592 593 594 595 596 597 598 599 600
      force = 0
      if syncml_data.count('\n') < self.MAX_LINES and (object.id.find('.')!=0): # If not we have to cut
        xml_object = self.getXMLObject(object=object,xml_mapping=domain.xml_mapping)
        LOG('getSyncMLData',0,'xml_mapping: %s' % str(domain.xml_mapping))
        LOG('getSyncMLData',0,'code: %s' % str(self.getAlertCode(remote_xml)))
        LOG('getSyncMLData',0,'gid_list: %s' % str(local_gid_list))
        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))
        signature = subscriber.getSignature(object_gid)
601
        LOG('getSyncMLData',0,'current object: %s' % str(object.getId()))
602
        # Here we first check if the object was modified or not by looking at dates
603
        if signature is not None:
604
          signature.checkSynchronizationNeeded(object)
605 606 607
        status = self.SENT
        more_data=0
        # For the case it was never synchronized, we have to send everything
608 609 610
        if signature is not None and signature.getXMLMapping()==None:
          pass
        elif signature==None or (signature.getXML()==None and signature.getStatus()!=self.PARTIAL) or \
611 612 613 614
            self.getAlertCode(remote_xml)==self.SLOW_SYNC:
          #LOG('PubSyncModif',0,'Current object.getPath: %s' % object.getPath())
          LOG('getSyncMLData',0,'no signature for gid: %s' % object_gid)
          xml_string = xml_object
615
          signature = Signature(gid=object_gid,id=object.getId())
616 617
          signature.setTempXML(xml_object)
          if xml_string.count('\n') > self.MAX_LINES:
618
            if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
619
              xml_string = xml_string.replace('--','@-@@-@')
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
            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):]
              #LOG('XMLSyncUtils',0,'rest_string: %s' % str(rest_string))
              i += 1
            LOG('getSyncMLData',0,'setPartialXML with: %s' % str(rest_string))
            signature.setPartialXML(rest_string)
            status =self.PARTIAL
            signature.setAction('Add')
            xml_string = '<!--' + short_string + '-->'
          syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object,gid=object_gid,
                                  xml_string=xml_string, more_data=more_data)
          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
          LOG('getSyncMLData',0,'checkMD5: %s' % str(signature.checkMD5(xml_object)))
          LOG('getSyncMLData',0,'getStatus: %s' % str(signature.getStatus()))
          if signature.getStatus()==self.PUB_CONFLICT_MERGE:
            xml_confirmation += self.SyncMLConfirmation(cmd_id,object.id,
                                  self.CONFLICT_MERGE,'Replace')
646
          set_synchronized = 1
647
          if not signature.checkMD5(xml_object):
648
            set_synchronized = 0
649 650 651 652 653
            # This object has changed on this side, we have to generate some xmldiff
            xml_string = self.getXupdateObject(object=object,
                                              xml_mapping=domain.xml_mapping,
                                              old_xml=signature.getXML())
            if xml_string.count('\n') > self.MAX_LINES:
654
              if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
655
                xml_string = xml_string.replace('--','@-@@-@')
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
              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)
            syncml_data += self.replaceXMLObject(cmd_id=cmd_id,object=object,gid=object_gid,
                                                xml_string=xml_string, more_data=more_data)
            cmd_id += 1
            signature.setTempXML(xml_object)
673 674 675 676 677 678 679 680 681 682 683 684
          # Now we can apply the xupdate from the subscriber
          subscriber_xupdate = signature.getSubscriberXupdate()
          LOG('getSyncMLData subscriber_xupdate',0,subscriber_xupdate)
          if subscriber_xupdate is not None:
            old_xml = signature.getXML()
            conduit.updateNode(xml=subscriber_xupdate, object=object,
                              previous_xml=old_xml,force=(domain.getDomainType==self.SUB),
                              simulate=0)
            xml_object = self.getXMLObject(object=object,xml_mapping=domain.xml_mapping)
            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
685 686 687 688 689 690 691
            signature.setStatus(self.SYNCHRONIZED)
        elif signature.getStatus()==self.PUB_CONFLICT_CLIENT_WIN:
          # We have decided to apply the update
          LOG('getSyncMLData',0,'signature.getTempXML(): %s' % str(signature.getTempXML()))
          # XXX previous_xml will be getXML instead of getTempXML because
          # some modification was already made and the update
          # may not apply correctly
692
          xml_update = signature.getPartialXML()
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
          conduit.updateNode(xml=signature.getPartialXML(), object=object,
                            previous_xml=signature.getXML(),force=1)
          xml_confirmation += self.SyncMLConfirmation(cmd_id,object_gid,
                                self.CONFLICT_CLIENT_WIN,'Replace')
          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
712
          if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
713
            xml_string = xml_string.replace('--','@-@@-@')
714 715 716 717 718 719 720 721
          xml_string = '<!--' + xml_string + '-->'
          signature.setStatus(status)
          if signature.getAction()=='Replace':
            syncml_data += self.replaceXMLObject(cmd_id=cmd_id,object=object,gid=object_gid,
                                                xml_string=xml_string, more_data=more_data)
          elif signature.getAction()=='Add':
            syncml_data += self.addXMLObject(cmd_id=cmd_id, object=object,gid=object_gid,
                                    xml_string=xml_string, more_data=more_data)
722
    return (syncml_data,xml_confirmation,cmd_id)
723 724

  def applyActionList(self, domain=None, subscriber=None,destination_path=None,
725
                      cmd_id=0,remote_xml=None,conduit=None,simulate=0):
726 727 728
    """
    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
729 730
    """
    next_action = self.getNextSyncAction(remote_xml, None)
731
    xml_confirmation = ''
Jean-Paul Smets's avatar
Jean-Paul Smets committed
732 733 734 735 736 737 738 739
    has_next_action = 0
    if next_action is not None:
      has_next_action = 1
    while next_action != None:
      conflict_list = []
      status_code = self.SUCCESS
      # Thirst we have to check the kind of action it is
      partial_data = self.getPartialData(next_action)
740 741
      object_gid = self.getActionId(next_action)
      signature = subscriber.getSignature(object_gid)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
742
      if signature == None:
743 744
        LOG('applyActionList, signature is None',0,signature)
        signature = Signature(gid=object_gid,status=self.NOT_SYNCHRONIZED).__of__(subscriber)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
745 746
        subscriber.addSignature(signature)
      force = signature.getForce()
747 748
      object = domain.getObjectFromGid(object_gid)
      LOG('applyActionList',0,'object: %s' % repr(object))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
749 750 751
      if self.checkActionMoreData(next_action) == 0:
        data_subnode = None
        if partial_data != None:
752 753 754 755 756
          signature_partial_xml = signature.getPartialXML()
          if signature_partial_xml is not None:
            data_subnode = signature.getPartialXML() + partial_data
          else:
            data_subnode = partial_data
757
          LOG('SyncModif',0,'data_subnode: %s' % data_subnode)
Sebastien Robin's avatar
Sebastien Robin committed
758 759
          #data_subnode = FromXml(data_subnode)
          data_subnode = parseString(data_subnode)
760
          data_subnode = data_subnode.childNodes[0] # Because we just created a new xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
761 762 763 764 765
          # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
        else:
          data_subnode = self.getDataSubNode(next_action)
        if next_action.nodeName == 'Add':
          # Then store the xml of this new subobject
766
          if object is None:
767
            object_id = domain.generateNewIdWithGenerator(object=destination_path)
768 769 770
            conflict_list += conduit.addNode(xml=data_subnode, object=destination_path,
                                             object_id=object_id)
            object = domain.getObjectFromGid(object_gid)
771
            LOG('applyActionList',0,'object after add: %s' % repr(object))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
772 773
          if object is not None:
            LOG('SyncModif',0,'addNode, found the object')
774 775 776 777
            #mapping = getattr(object,domain.getXMLMapping(),None)
            xml_object = domain.getXMLFromObject(object)
            #if mapping is not None:
            #  xml_object = mapping()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
778
            signature.setStatus(self.SYNCHRONIZED)
779
            signature.setId(object.getId())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
780
            signature.setXML(xml_object)
781 782
            xml_confirmation +=\
                 self.SyncMLConfirmation(cmd_id,object_gid,self.SUCCESS,'Add')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
783 784 785 786 787
            cmd_id +=1
        elif next_action.nodeName == 'Replace':
          LOG('SyncModif',0,'object: %s will be updated...' % str(object))
          if object is not None:
            LOG('SyncModif',0,'object: %s will be updated...' % object.id)
788
            signature = subscriber.getSignature(object_gid)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
789 790
            LOG('SyncModif',0,'previous signature: %s' % str(signature))
            previous_xml = signature.getXML()
791
            #LOG('SyncModif',0,'previous signature: %i' % len(previous_xml))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
792
            conflict_list += conduit.updateNode(xml=data_subnode, object=object,
793 794
                              previous_xml=signature.getXML(),force=force,
                              simulate=simulate)
795 796 797 798
            #mapping = getattr(object,domain.getXMLMapping(),None)
            xml_object = domain.getXMLFromObject(object)
            #if mapping is not None:
            #  xml_object = mapping()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
799 800 801 802 803 804 805 806 807
            signature.setTempXML(xml_object)
            if conflict_list != []:
              status_code = self.CONFLICT
              signature.setStatus(self.CONFLICT)
              signature.setConflictList(signature.getConflictList()+conflict_list)
              string_io = StringIO()
              PrettyPrint(data_subnode,stream=string_io)
              data_subnode_string = string_io.getvalue()
              signature.setPartialXML(data_subnode_string)
808
            elif not simulate:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
809 810
              signature.setStatus(self.SYNCHRONIZED)
            xml_confirmation += self.SyncMLConfirmation(cmd_id,
811
                                        object_gid,status_code,'Replace')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
812
            cmd_id +=1
813 814 815 816 817 818 819 820 821 822
            if simulate:
              # This means we are on the publiher side and we want to store
              # 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()
              LOG('applyActionList, subscriber_xupdate:',0,data_subnode_string)
              signature.setSubscriberXupdate(data_subnode_string)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
823
        elif next_action.nodeName == 'Delete':
824
          object_id = signature.getId()
825
          conduit.deleteNode(xml=self.getDataSubNode(next_action), object=destination_path,
826
                             object_id=object_id)
827
          subscriber.delSignature(object_gid)
828 829
          xml_confirmation += self.SyncMLConfirmation(cmd_id,
                                      object_gid,status_code,'Delete')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
830 831 832 833
      else: # We want to retrieve more data
        signature.setStatus(self.PARTIAL)
        #LOG('SyncModif',0,'setPartialXML: %s' % str(previous_partial))
        previous_partial = signature.getPartialXML() or ''
834 835
        #if previous_partial.find(partial_data)<0: # XXX bad thing
        previous_partial += partial_data
Jean-Paul Smets's avatar
Jean-Paul Smets committed
836
        signature.setPartialXML(previous_partial)
837 838
        #LOG('SyncModif',0,'previous_partial: %s' % str(previous_partial))
        LOG('SyncModif',0,'waiting more data for :%s' % signature.getId())
839
        xml_confirmation += self.SyncMLConfirmation(cmd_id,object_gid,
Jean-Paul Smets's avatar
Jean-Paul Smets committed
840 841 842 843 844 845
                                                self.WAITING_DATA,next_action.nodeName)
      if conflict_list != [] and signature is not None:
        # We had a conflict
        signature.setStatus(self.CONFLICT)

      next_action = self.getNextSyncAction(remote_xml, next_action)
846 847 848 849 850 851 852 853
    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
    """
    next_status = self.getNextSyncBodyStatus(remote_xml, None)
854
    LOG('applyStatusList, next_status',0,next_status)
855 856 857 858 859 860 861 862 863
    # We do not want the first one
    next_status = self.getNextSyncBodyStatus(remote_xml, next_status)
    has_status_list = 0
    if next_status is not None:
      has_status_list = 1
    destination_waiting_more_data = 0
    while next_status != None:
      object_gid = self.getStatusTarget(next_status)
      status_code = self.getStatusCode(next_status)
864
      status_cmd = self.getStatusCommand(next_status)
865 866
      signature = subscriber.getSignature(object_gid)
      LOG('SyncModif',0,'next_status: %s' % str(status_code))
867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
      if status_cmd in ('Add','Replace'):
        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 == self.SUCCESS:
          signature.setStatus(self.SYNCHRONIZED)
      elif status_cmd == 'Delete':
        if status_code == self.SUCCESS:
          subscriber.delSignature(object_gid)
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914
      next_status = self.getNextSyncBodyStatus(remote_xml, next_status)
    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

  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
    """
915 916
    #from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
    from Products.ERP5SyncML import Conduit
917
    has_response = 0 #check if syncmodif replies to this messages
918 919 920 921 922
    cmd_id = 1 # specifies a SyncML message-unique command identifier
    LOG('SyncModif',0,'Starting... domain: %s' % str(domain))
    # Get the destination folder
    destination_path = self.unrestrictedTraverse(domain.getDestinationPath())

Sebastien Robin's avatar
Sebastien Robin committed
923
    first_node = remote_xml.childNodes[0]
924 925 926 927 928 929 930
    # Get informations from the header
    xml_header = first_node.childNodes[1]
    if xml_header.nodeName != "SyncHdr":
      LOG('PubSyncModif',0,'This is not a SyncML Header')
      return

    subscriber = domain # If we are the client, this is fine
931
    simulate = 0 # used by applyActionList, should be 0 for client
932
    if domain.domain_type == self.PUB:
933
      simulate = 1
934 935 936 937 938
      for subnode in xml_header.childNodes:
        if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Source":
          subscription_url = str(subnode.childNodes[0].data)
      subscriber = domain.getSubscriber(subscription_url)

939 940 941 942 943 944 945 946 947 948
    # We have to check if this message was not already, this can be dangerous
    # to update two times the same object
    session_id = self.getSessionId(remote_xml)
    correct_session = subscriber.checkCorrectRemoteSessionId(session_id)
    if not correct_session: # We need to send again the message
      last_xml = subscriber.getLastSentMessage()
      if last_xml != '':
        has_response = 1
        if domain.domain_type == self.PUB: # We always reply
          self.sendResponse(from_url=domain.publication_url, to_url=subscriber.subscription_url,
949
                    sync_id=domain.getTitle(), xml=last_xml,domain=domain)
950 951
        elif domain.domain_type == self.SUB:
          self.sendResponse(from_url=domain.subscription_url, to_url=domain.publication_url,
952
              sync_id=domain.getTitle(), xml=last_xml,domain=domain)
953 954 955
      return {'has_response':has_response,'xml':last_xml}
    subscriber.setLastSentMessage('')

956 957 958 959 960 961
    # 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)
962 963 964
    #conduit = ERP5Conduit()
    conduit_name = subscriber.getConduit()
    conduit = getattr(getattr(Conduit,conduit_name),conduit_name)()
965 966 967 968 969 970 971
    LOG('SyncModif, subscriber: ',0,subscriber)
    # Then apply the list of actions
    (xml_confirmation,has_next_action,cmd_id) = self.applyActionList(cmd_id=cmd_id,
                                         domain=domain,
                                         destination_path=destination_path,
                                         subscriber=subscriber,
                                         remote_xml=remote_xml,
972
                                         conduit=conduit, simulate=simulate)
973
    LOG('SyncModif, has_next_action:',0,has_next_action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989

    xml = ""
    xml += '<SyncML>\n'

    # syncml header
    if domain.domain_type == self.PUB:
      xml += self.SyncMLHeader(subscriber.getSessionId(), "1",
          subscriber.getSubscriptionUrl(), domain.getPublicationUrl())
    elif domain.domain_type == self.SUB:
      xml += self.SyncMLHeader(domain.getSessionId(), "1",
        domain.getPublicationUrl(), domain.getSubscriptionUrl())


    cmd_id += 1
    # Add or replace objects
    syncml_data = ''
990
    # Now we have to send our own modifications
991 992
    if has_next_action == 0 and not \
      (domain.domain_type==self.SUB and alert_code==self.SLOW_SYNC):
993 994
      (syncml_data,xml_confirmation,cmd_id) = self.getSyncMLData(domain=domain,
                                       remote_xml=remote_xml,
995 996
                                       subscriber=subscriber,
                                       destination_path=destination_path,
997 998
                                       cmd_id=cmd_id,xml_confirmation=xml_confirmation,
                                       conduit=conduit)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030

    # syncml body
    xml += ' <SyncBody>\n'
    destination_url = ''
    if domain.domain_type == self.PUB:
      subscriber.NewAnchor()
      destination_url = domain.getPublicationUrl()
      xml += self.SyncMLStatus(cmd_id, subscriber.getSubscriptionUrl(),
                               domain.getDestinationPath(),
                               subscriber.getSynchronizationType(),
                               subscriber.getNextAnchor())
    elif domain.domain_type == self.SUB:
      destination_url = domain.getPublicationUrl()
      xml += self.SyncMLStatus(cmd_id, domain.getPublicationUrl(),
                               subscriber.getDestinationPath(),
                               subscriber.getSynchronizationType(),
                               subscriber.getNextAnchor())
    # alert message if we want more data
    if destination_waiting_more_data == 1:
      xml += self.SyncMLAlert(cmd_id, self.WAITING_DATA,
                              destination_url,
                              domain.getDestinationPath(),
                              subscriber.getLastAnchor(), subscriber.getNextAnchor())
    # Now we should send confirmations
    xml += xml_confirmation
    if syncml_data != '':
      xml += '  <Sync>\n'
      xml += syncml_data
      xml += '  </Sync>\n'
    xml += '  <Final/>\n'
    xml += ' </SyncBody>\n'
    xml += '</SyncML>\n'
1031
    if domain.domain_type == self.PUB: # We always reply
1032
      subscriber.setLastSentMessage(xml)
1033
      self.sendResponse(from_url=domain.publication_url, to_url=subscriber.subscription_url,
1034
                sync_id=domain.getTitle(), xml=xml,domain=domain)
1035 1036 1037 1038 1039
      has_response = 1
    elif domain.domain_type == self.SUB:
      if self.checkAlert(remote_xml) or \
         (xml_confirmation,syncml_data)!=('','') or \
          has_status_list:
1040
        subscriber.setLastSentMessage(xml)
1041
        self.sendResponse(from_url=domain.subscription_url, to_url=domain.publication_url,
1042
            sync_id=domain.getTitle(), xml=xml,domain=domain)
1043
        has_response = 1
1044
    return {'has_response':has_response,'xml':xml}