XMLSyncUtils.py 51.3 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

Sebastien Robin's avatar
Sebastien Robin committed
49
  def SyncMLHeader(self, session_id, msg_id, target, source, target_name=None, 
50 51
      source_name=None, dataCred=None, authentication_format='b64', 
      authentication_type='syncml:auth-basic'):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
52 53 54 55
    """
      Since the Header is always almost the same, this is the
      way to set one quickly.
    """
Sebastien Robin's avatar
Sebastien Robin committed
56 57 58 59 60 61 62 63 64
    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)
65
    if target_name not in (None, ''):
Sebastien Robin's avatar
Sebastien Robin committed
66 67 68 69
      xml('   <LocName>%s</LocName>\n' %target_name)
    xml('  </Target>\n')
    xml('  <Source>\n')
    xml('   <LocURI>%s</LocURI>\n' % source) 
70
    if source_name not in (None, ''):
Sebastien Robin's avatar
Sebastien Robin committed
71 72
      xml('   <LocName>%s</LocName>\n' % source_name)
    xml('  </Source>\n')
73 74 75 76 77 78 79 80
    if dataCred not in (None, ''):
      xml('  <Cred>\n')
      xml('   <Meta>\n')
      xml('    <Format>%s</Format>\n' % authentication_format)
      xml('    <Type>%s</Type>\n' % authentication_type)
      xml('   </Meta>\n')
      xml('   <Data>%s</Data>\n' % dataCred)
      xml('  </Cred>\n')
Sebastien Robin's avatar
Sebastien Robin committed
81 82 83
    xml(' </SyncHdr>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
84

Sebastien Robin's avatar
Sebastien Robin committed
85 86
  def SyncMLAlert(self, cmd_id, sync_code, target, source, last_anchor, 
      next_anchor):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
87 88 89 90
    """
      Since the Alert section is always almost the same, this is the
      way to set one quickly.
    """
Sebastien Robin's avatar
Sebastien Robin committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    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')
    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')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
113

Sebastien Robin's avatar
Sebastien Robin committed
114
  def SyncMLStatus(self, cmd_id, target_ref, source_ref, sync_code, 
115
      next_anchor=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
116 117 118 119
    """
      Since the Status section is always almost the same, this is the
      way to set one quickly.
    """
Sebastien Robin's avatar
Sebastien Robin committed
120 121 122 123 124 125 126
    xml_list = []
    xml = xml_list.append
    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)
127 128 129 130 131 132 133 134
    if next_anchor is not None:
      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')
Sebastien Robin's avatar
Sebastien Robin committed
135 136 137
    xml('  </Status>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
138 139 140

  def SyncMLConfirmation(self, cmd_id, target_ref, sync_code, cmd):
    """
Sebastien Robin's avatar
Sebastien Robin committed
141
    This is used in order to confirm that an object was correctly
Jean-Paul Smets's avatar
Jean-Paul Smets committed
142 143
    synchronized
    """
Sebastien Robin's avatar
Sebastien Robin committed
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
    xml_list = []
    xml = xml_list.append
    xml('  <Status>\n')
    xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    xml('   <TargetRef>%s</TargetRef>\n' % target_ref)
    xml('   <Cmd>%s</Cmd>\n' % cmd)
    xml('   <Data>%s</Data>\n' % sync_code)
    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)
    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')
    xml('     <Format>%s</Format>\n' % auth_format)
    xml('     <Type>%s</Type>\n' % auth_type)
    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
177 178 179 180 181 182 183 184 185 186 187 188 189 190

  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)
191
    #server.sendmail(fromaddr, "seb@localhost", msg)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
192 193
    server.quit()

194 195
  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
196 197 198
    """
      Add an object with the SyncML protocol
    """
Sebastien Robin's avatar
Sebastien Robin committed
199 200 201 202 203 204 205 206 207 208 209 210
    xml_list = []
    xml = xml_list.append
    xml('   <Add>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
    xml('    <Meta><Type>%s</Type></Meta>\n' % object.portal_type)
    xml('    <Item>\n')
    xml('     <Source>\n')
    xml('      <LocURI>%s</LocURI>\n' % gid)
    xml('     </Source>\n')
    xml('     <Data>\n')
    xml(xml_string)
    xml('     </Data>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
211
    if more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
212 213 214 215 216
      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
217

218
  def deleteXMLObject(self, cmd_id=0, object_gid=None, xml_object=''):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
219
    """
Sebastien Robin's avatar
Sebastien Robin committed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
      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')
    xml('     <Source>\n')
    xml('      <LocURI>%s</LocURI>\n' % object_gid)
    xml('     </Source>\n')
    xml('     <Data>\n')
    xml('     </Data>\n')
    xml('    </Item>\n')
    xml('   </Delete>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
236

237 238
  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
239
    """
Sebastien Robin's avatar
Sebastien Robin committed
240 241 242 243 244 245 246 247 248 249 250 251 252 253
      Replace an object with the SyncML protocol
    """
    xml_list = []
    xml = xml_list.append
    xml('   <Replace>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
    xml('    <Meta><Type>%s</Type></Meta>\n' % object.portal_type)
    xml('    <Item>\n')
    xml('     <Source>\n')
    xml('      <LocURI>%s</LocURI>\n' % str(gid))
    xml('     </Source>\n')
    xml('     <Data>\n')
    xml(xml_string)
    xml('     </Data>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
254
    if more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
255 256 257 258 259
      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
260 261 262 263 264 265 266 267

  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
    """
268 269 270 271
    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
272 273
    file1.write(self.getXMLObject(object=object,xml_mapping=xml_mapping))
    file1.close()
274
    file2 = open('/tmp/%s'% old_filename,'w')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
275 276
    file2.write(old_xml)
    file2.close()
Sebastien Robin's avatar
Sebastien Robin committed
277 278
    xupdate = commands.getoutput('erp5diff /tmp/%s /tmp/%s' % 
        (old_filename,new_filename))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
279
    xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
280 281
    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
282 283 284 285 286 287 288 289
    return xupdate

  def getXMLObject(self, object=None, xml_mapping=None):
    """
    This just allow to get the xml of the object
    """
    xml_method = None
    xml = ""
290 291 292 293 294 295 296
    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
297 298
    return xml

299 300 301 302 303 304 305 306 307 308 309 310 311
  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
Sebastien Robin's avatar
Sebastien Robin committed
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
    
    
  def getMessageId(self, xml):
    """
    We will retrieve the message id of the message
    """
    message_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 == 'MsgID':
                message_id = int(subnode2.childNodes[0].data)
    return message_id

  def getTarget(self, xml):
    """
    return the target in the SyncHdr section
    """
    url = ''
    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 == 'Target':
                for subnode3 in self.getElementNodeList(subnode2):
                  if subnode3.nodeName == 'LocURI':
                    url = subnode3.childNodes[0].data
    return url

344

Jean-Paul Smets's avatar
Jean-Paul Smets committed
345 346 347 348 349
  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
350
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
351 352 353 354

    # Get informations from the body
    client_body = first_node.childNodes[3]
    for subnode in client_body.childNodes:
Sebastien Robin's avatar
Sebastien Robin committed
355 356
      if subnode.nodeType == subnode.ELEMENT_NODE and \
          subnode.nodeName == "Alert":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
357
        for subnode2 in subnode.childNodes:
Sebastien Robin's avatar
Sebastien Robin committed
358 359
          if subnode2.nodeType == subnode2.ELEMENT_NODE and \
              subnode2.nodeName == "Item":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
360
            for subnode3 in subnode2.childNodes:
Sebastien Robin's avatar
Sebastien Robin committed
361 362 363 364 365
              if subnode3.nodeType == subnode3.ELEMENT_NODE and \
                  subnode3.nodeName == "Meta":
                for subnode4 in subnode3.childNodes:
                  if subnode4.nodeType == subnode4.ELEMENT_NODE and \
                      subnode4.nodeName == "Anchor":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
366 367
                    for subnode5 in subnode4.childNodes:
                      # Get the last time we had a synchronization
Sebastien Robin's avatar
Sebastien Robin committed
368 369
                     if subnode5.nodeType == subnode5.ELEMENT_NODE and \
                         subnode5.nodeName == "Last":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
370
                        last_anchor = subnode5.childNodes[0].data
Sebastien Robin's avatar
Sebastien Robin committed
371
    return last_anchor
Jean-Paul Smets's avatar
Jean-Paul Smets committed
372 373 374 375 376 377

  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
378
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
379 380 381 382 383 384 385 386
    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:
Sebastien Robin's avatar
Sebastien Robin committed
387 388
      if subnode.nodeType == subnode.ELEMENT_NODE and \
          subnode.nodeName == "Alert":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
389
        for subnode2 in subnode.childNodes:
Sebastien Robin's avatar
Sebastien Robin committed
390 391
          if subnode2.nodeType == subnode2.ELEMENT_NODE and \
              subnode2.nodeName == "Item":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
392
            for subnode3 in subnode2.childNodes:
Sebastien Robin's avatar
Sebastien Robin committed
393 394
              if subnode3.nodeType == subnode3.ELEMENT_NODE and \
                  subnode3.nodeName == "Meta":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
395
                for subnode4 in subnode3.childNodes:
Sebastien Robin's avatar
Sebastien Robin committed
396 397
                 if subnode4.nodeType == subnode4.ELEMENT_NODE and \
                     subnode4.nodeName == "Anchor":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
398 399
                    for subnode5 in subnode4.childNodes:
                      # Get the last time we had a synchronization
Sebastien Robin's avatar
Sebastien Robin committed
400 401
                      if subnode5.nodeType == subnode5.ELEMENT_NODE and \
                          subnode5.nodeName == "Next":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
402 403 404 405 406 407 408 409 410 411
                        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:
Sebastien Robin's avatar
Sebastien Robin committed
412 413
        if subnode.nodeType == subnode.ELEMENT_NODE and \
            subnode.nodeName == 'TargetRef':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
414 415 416 417 418 419 420 421 422 423
          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:
Sebastien Robin's avatar
Sebastien Robin committed
424 425
        if subnode.nodeType == subnode.ELEMENT_NODE and \
            subnode.nodeName == 'Data':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
426 427 428
          return int(subnode.childNodes[0].data)
    return None

429 430 431 432 433 434 435 436 437 438 439
  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

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 465 466 467 468 469 470 471 472 473 474 475 476 477
  def getCred(self, xml):
    """
      return the credential information : type, format and data
    """    
    format=''
    type=''
    data=''
    

    first_node = xml.childNodes[0]
    if first_node.nodeName != "SyncML":
      print "This is not a SyncML message"
    # 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')
      raise ValueError, "Sorry, This is not a SyncML Header"

    for subnode in xml_header.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName=='Cred':
        for subnode2 in subnode.childNodes:
          if subnode2.nodeType == subnode2.ELEMENT_NODE and \
              subnode2.nodeName == 'Meta':
            for subnode3 in subnode2.childNodes:
              if subnode3.nodeType == subnode3.ELEMENT_NODE and \
                  subnode3.nodeName == 'Format':
                    if len(subnode3.childNodes) > 0:
                      format=subnode3.childNodes[0].data
              if subnode3.nodeType == subnode3.ELEMENT_NODE and \
                  subnode3.nodeName == 'Type':
                    if len(subnode3.childNodes) > 0:
                      type=subnode3.childNodes[0].data
          if subnode2.nodeType == subnode2.ELEMENT_NODE and \
              subnode2.nodeName == 'Data':
                if len(subnode2.childNodes) > 0:
                  data=subnode2.childNodes[0].data
    return (format, type, data)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
478 479 480 481 482
  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
483
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
484 485 486
    client_body = first_node.childNodes[3]
    if client_body.nodeName != "SyncBody":
      LOG('XMLSyncUtils.getAlertCode',0,"This is not a SyncML Body")
Sebastien Robin's avatar
Sebastien Robin committed
487
      raise ValueError, "Sorry, This is not a SyncML Body"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
488 489 490 491 492 493 494 495
    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

Sebastien Robin's avatar
Sebastien Robin committed
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
  def checkCred(self, xml_stream):
    """
      Check if there's a Cred section in the xml_stream
    """
    first_node = xml_stream.childNodes[0]
    # 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')
      raise ValueError, "Sorry, This is not a SyncML Header"
    cred = 0
    for subnode in xml_header.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Cred":
        cred=1
    return cred

Jean-Paul Smets's avatar
Jean-Paul Smets committed
512 513
  def checkAlert(self, xml_stream):
    """
Sebastien Robin's avatar
Sebastien Robin committed
514
      Check if there's an Alert section in the xml_stream
Jean-Paul Smets's avatar
Jean-Paul Smets committed
515
    """
Sebastien Robin's avatar
Sebastien Robin committed
516
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
517 518 519 520 521 522 523 524 525 526 527 528 529
    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
530
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
531 532 533
    client_body = first_node.childNodes[3]
    if client_body.nodeName != "SyncBody":
      LOG('checkSync',0,"This is not a SyncML Body")
Sebastien Robin's avatar
Sebastien Robin committed
534
      raise ValueError, "Sorry, This is not a SyncML Body"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
535 536 537 538 539 540 541 542 543
    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
544
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
    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
560
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
    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
588
    first_node = xml_stream.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
589 590 591
    client_body = first_node.childNodes[3]
    if client_body.nodeName != "SyncBody":
      LOG('getNextSyncBodyStatus',0,"This is not a SyncML Body")
Sebastien Robin's avatar
Sebastien Robin committed
592
      raise ValueError, "Sorry, This is not a SyncML Body"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
    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('-->')]
635
                xml = subnode3.data
Sebastien Robin's avatar
Sebastien Robin committed
636
		if isinstance(xml, unicode):
637 638
                  xml = xml.encode('utf-8')
                return xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685

    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)

686 687 688 689
  def getElementNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
Sebastien Robin's avatar
Sebastien Robin committed
690
    #return node.getElementsByTagName('*')
691
    subnode_list = []
692
    for subnode in node.childNodes or []:
693 694 695 696
      if subnode.nodeType == subnode.ELEMENT_NODE:
        subnode_list += [subnode]
    return subnode_list

697 698 699 700 701
  def getTextNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
    subnode_list = []
702
    for subnode in node.childNodes or []:
703 704 705 706
      if subnode.nodeType == subnode.TEXT_NODE:
        subnode_list += [subnode]
    return subnode_list

707 708 709 710 711
  def getAttributeNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
    attribute_list = []
Sebastien Robin's avatar
Sebastien Robin committed
712
    for subnode in node.attributes.values() or []:
713 714 715 716
      if subnode.nodeType == subnode.ATTRIBUTE_NODE:
        attribute_list += [subnode]
    return attribute_list

717
  def getSyncMLData(self, domain=None,remote_xml=None,cmd_id=0,
718
                          subscriber=None,destination_path=None,
719
                          xml_confirmation=None,conduit=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
720
    """
721 722
    This generate the syncml data message. This returns a string
    with all modification made locally (ie replace, add ,delete...)
723 724 725

    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
726
    """
727 728
    local_gid_list = []
    syncml_data = ''
729

730
    if subscriber.getRemainingObjectPathList() is None:
731
      object_list = domain.getObjectList()
732 733 734
      object_path_list = map(lambda x: x.getPhysicalPath(),object_list)
      LOG('getSyncMLData, object_path_list',0,object_path_list)
      subscriber.setRemainingObjectPathList(object_path_list)
735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751

      #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)

752
    local_gid_list = []
753
    #for object in domain.getObjectList():
754 755 756 757 758 759 760
    for object_path in subscriber.getRemainingObjectPathList():
      #object = subscriber.getDestination()._getOb(object_id)
      #object = subscriber.getDestination()._getOb(object_id)
      #try:
      object = self.unrestrictedTraverse(object_path)
      #except KeyError:
      #object = None
761
      status = self.SENT
762 763 764 765 766
      #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()
767
      force = 0
768
      if syncml_data.count('\n') < self.MAX_LINES and not object.id.startswith('.'): # If not we have to cut
769 770 771 772 773
        #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))
774
        signature = subscriber.getSignature(object_gid)
775
        #LOG('getSyncMLData',0,'current object: %s' % str(object.getId()))
776
        # Here we first check if the object was modified or not by looking at dates
777
        if signature is not None:
778
          signature.checkSynchronizationNeeded(object)
779 780 781
        status = self.SENT
        more_data=0
        # For the case it was never synchronized, we have to send everything
782 783 784
        if signature is not None and signature.getXMLMapping()==None:
          pass
        elif signature==None or (signature.getXML()==None and signature.getStatus()!=self.PARTIAL) or \
785 786 787
            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)
788
          xml_object = self.getXMLObject(object=object,xml_mapping=domain.xml_mapping)
789
          xml_string = xml_object
790
          signature = Signature(gid=object_gid,id=object.getId(),object=object)
791 792
          signature.setTempXML(xml_object)
          if xml_string.count('\n') > self.MAX_LINES:
793
            if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
794
              xml_string = xml_string.replace('--','@-@@-@')
795 796 797 798 799 800 801 802 803
            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
804
            #LOG('getSyncMLData',0,'setPartialXML with: %s' % str(rest_string))
805 806 807 808 809 810 811 812 813 814 815
            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
816
          xml_object = self.getXMLObject(object=object,xml_mapping=domain.xml_mapping)
817 818
          #LOG('getSyncMLData',0,'checkMD5: %s' % str(signature.checkMD5(xml_object)))
          #LOG('getSyncMLData',0,'getStatus: %s' % str(signature.getStatus()))
819 820 821
          if signature.getStatus()==self.PUB_CONFLICT_MERGE:
            xml_confirmation += self.SyncMLConfirmation(cmd_id,object.id,
                                  self.CONFLICT_MERGE,'Replace')
822
          set_synchronized = 1
823
          if not signature.checkMD5(xml_object):
824
            set_synchronized = 0
825 826 827 828 829
            # 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:
830
              if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
831
                xml_string = xml_string.replace('--','@-@@-@')
832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
              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)
849 850
          # Now we can apply the xupdate from the subscriber
          subscriber_xupdate = signature.getSubscriberXupdate()
851
          #LOG('getSyncMLData subscriber_xupdate',0,subscriber_xupdate)
852 853 854 855 856 857 858 859 860
          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
861 862 863 864 865 866 867
            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
868
          xml_update = signature.getPartialXML()
869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887
          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
888
          if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
889
            xml_string = xml_string.replace('--','@-@@-@')
890 891 892 893 894 895 896 897
          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)
898
    return (syncml_data,xml_confirmation,cmd_id)
899 900

  def applyActionList(self, domain=None, subscriber=None,destination_path=None,
901
                      cmd_id=0,remote_xml=None,conduit=None,simulate=0):
902 903 904
    """
    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
905 906
    """
    next_action = self.getNextSyncAction(remote_xml, None)
907
    xml_confirmation = ''
Jean-Paul Smets's avatar
Jean-Paul Smets committed
908 909 910 911 912 913 914 915
    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)
916 917
      object_gid = self.getActionId(next_action)
      signature = subscriber.getSignature(object_gid)
918
      object = domain.getObjectFromGid(object_gid)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
919
      if signature == None:
920
        LOG('applyActionList, signature is None',0,signature)
921
        signature = Signature(gid=object_gid,status=self.NOT_SYNCHRONIZED,object=object).__of__(subscriber)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
922 923
        subscriber.addSignature(signature)
      force = signature.getForce()
924
      LOG('applyActionList',0,'object: %s' % repr(object))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
925 926 927
      if self.checkActionMoreData(next_action) == 0:
        data_subnode = None
        if partial_data != None:
928 929 930 931 932
          signature_partial_xml = signature.getPartialXML()
          if signature_partial_xml is not None:
            data_subnode = signature.getPartialXML() + partial_data
          else:
            data_subnode = partial_data
933
          LOG('SyncModif',0,'data_subnode: %s' % data_subnode)
Sebastien Robin's avatar
Sebastien Robin committed
934 935
          #data_subnode = FromXml(data_subnode)
          data_subnode = parseString(data_subnode)
936
          data_subnode = data_subnode.childNodes[0] # Because we just created a new xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
937 938 939 940 941
          # 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
942
          if object is None:
943
            object_id = domain.generateNewIdWithGenerator(object=destination_path,gid=object_gid)
944
            #if object_id is not None:
945
            add_data = conduit.addNode(xml=data_subnode, object=destination_path,
946
                                             object_id=object_id)
947 948 949 950
            conflict_list += add_data['conflict_list']
            # Retrieve directly the object from addNode
            object = add_data['object']
            LOG('XMLSyncUtils, in ADD add_data',0,add_data)
951
            LOG('applyActionList',0,'object after add: %s' % repr(object))
952 953 954 955 956 957 958 959 960
          else:
            #Object was retrieve but need to be updated without recreated
            #usefull when an object is only deleted by workflow.
            object_id = domain.generateNewIdWithGenerator(object=destination_path,gid=object_gid)
            add_data = conduit.addNode(xml=data_subnode,
                                       object=destination_path,
                                       object_id=object_id,
                                       sub_object=object)
            conflict_list += add_data['conflict_list']
Jean-Paul Smets's avatar
Jean-Paul Smets committed
961 962
          if object is not None:
            LOG('SyncModif',0,'addNode, found the object')
963 964 965 966
            #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
967
            signature.setStatus(self.SYNCHRONIZED)
968
            signature.setId(object.getId())
969
            signature.setPath(object.getPhysicalPath())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
970
            signature.setXML(xml_object)
971 972
            xml_confirmation +=\
                 self.SyncMLConfirmation(cmd_id,object_gid,self.SUCCESS,'Add')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
973 974 975 976 977
            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)
978
            signature = subscriber.getSignature(object_gid)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
979 980
            LOG('SyncModif',0,'previous signature: %s' % str(signature))
            previous_xml = signature.getXML()
981
            #LOG('SyncModif',0,'previous signature: %i' % len(previous_xml))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
982
            conflict_list += conduit.updateNode(xml=data_subnode, object=object,
983 984
                              previous_xml=signature.getXML(),force=force,
                              simulate=simulate)
985 986 987 988
            #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
989 990 991 992 993 994 995 996 997
            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)
998
            elif not simulate:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
999 1000
              signature.setStatus(self.SYNCHRONIZED)
            xml_confirmation += self.SyncMLConfirmation(cmd_id,
1001
                                        object_gid,status_code,'Replace')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1002
            cmd_id +=1
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
            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
1013
        elif next_action.nodeName == 'Delete':
1014
          object_id = signature.getId()
1015
          conduit.deleteNode(xml=self.getDataSubNode(next_action), object=destination_path,
1016
                             object_id=object_id)
1017
          subscriber.delSignature(object_gid)
1018 1019
          xml_confirmation += self.SyncMLConfirmation(cmd_id,
                                      object_gid,status_code,'Delete')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1020 1021 1022 1023
      else: # We want to retrieve more data
        signature.setStatus(self.PARTIAL)
        #LOG('SyncModif',0,'setPartialXML: %s' % str(previous_partial))
        previous_partial = signature.getPartialXML() or ''
1024 1025
        #if previous_partial.find(partial_data)<0: # XXX bad thing
        previous_partial += partial_data
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1026
        signature.setPartialXML(previous_partial)
1027 1028
        #LOG('SyncModif',0,'previous_partial: %s' % str(previous_partial))
        LOG('SyncModif',0,'waiting more data for :%s' % signature.getId())
1029
        xml_confirmation += self.SyncMLConfirmation(cmd_id,object_gid,
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1030 1031 1032 1033 1034 1035
                                                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)
1036 1037 1038 1039 1040 1041 1042 1043
    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)
1044
    LOG('applyStatusList, next_status',0,next_status)
1045 1046 1047 1048 1049 1050 1051 1052 1053
    # 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)
1054
      status_cmd = self.getStatusCommand(next_status)
1055 1056
      signature = subscriber.getSignature(object_gid)
      LOG('SyncModif',0,'next_status: %s' % str(status_code))
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
      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)
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104
      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
    """
1105
    from Products.ERP5SyncML import Conduit
1106
    has_response = 0 #check if syncmodif replies to this messages
1107 1108 1109 1110 1111
    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
1112
    first_node = remote_xml.childNodes[0]
1113 1114 1115 1116
    # 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')
Sebastien Robin's avatar
Sebastien Robin committed
1117
      raise ValueError, "Sorry, This is not a SyncML Header"
1118 1119

    subscriber = domain # If we are the client, this is fine
1120
    simulate = 0 # used by applyActionList, should be 0 for client
1121
    if domain.domain_type == self.PUB:
1122
      simulate = 1
1123 1124
      for subnode in xml_header.childNodes:
        if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName == "Source":
Sebastien Robin's avatar
Sebastien Robin committed
1125 1126 1127
	  for subnode2 in subnode.childNodes:
	    if subnode2.nodeType == subnode2.ELEMENT_NODE and subnode2.nodeName == 'LocURI':
	      subscription_url = str(subnode2.childNodes[0].data)
1128 1129
      subscriber = domain.getSubscriber(subscription_url)

1130 1131
    # 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
1132 1133 1134 1135
    message_id = self.getMessageId(remote_xml)
    correct_message = subscriber.checkCorrectRemoteMessageId(message_id)
    if not correct_message: # We need to send again the message
      LOG('SyncModif, no correct message:',0,"sending again...")
1136 1137 1138 1139
      last_xml = subscriber.getLastSentMessage()
      if last_xml != '':
        has_response = 1
        if domain.domain_type == self.PUB: # We always reply
1140 1141 1142
          self.sendResponse(from_url=domain.publication_url, 
              to_url=subscriber.subscription_url, sync_id=domain.getTitle(), 
              xml=last_xml,domain=domain)
1143
        elif domain.domain_type == self.SUB:
1144 1145
          self.sendResponse(from_url=domain.subscription_url, 
              to_url=domain.publication_url, sync_id=domain.getTitle(), xml=last_xml,domain=domain)
1146 1147 1148
      return {'has_response':has_response,'xml':last_xml}
    subscriber.setLastSentMessage('')

1149 1150 1151 1152 1153 1154
    # 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
1155
    # Import the conduit and get it
Nicolas Delaby's avatar
Nicolas Delaby committed
1156
    conduit_name = subscriber.getConduit()
1157 1158 1159 1160 1161 1162
    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:
Nicolas Delaby's avatar
Nicolas Delaby committed
1163
      conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]),
1164 1165
                                  globals(), locals(), [''])
      conduit = getattr(conduit_module, conduit_name)()
1166 1167 1168 1169 1170 1171
    # 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,
1172
                                         conduit=conduit, simulate=simulate)
1173
    #LOG('SyncModif, has_next_action:',0,has_next_action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1174

Sebastien Robin's avatar
Sebastien Robin committed
1175 1176 1177 1178
    xml_list = []
    xml = xml_list.append
    xml('<SyncML>\n')
    
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1179 1180
    # syncml header
    if domain.domain_type == self.PUB:
Sebastien Robin's avatar
Sebastien Robin committed
1181 1182
      xml(self.SyncMLHeader(subscriber.getSessionId(), subscriber.incrementMessageId(),
          subscriber.getSubscriptionUrl(), domain.getPublicationUrl()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1183
    elif domain.domain_type == self.SUB:
Sebastien Robin's avatar
Sebastien Robin committed
1184 1185
      xml(self.SyncMLHeader(domain.getSessionId(), domain.incrementMessageId(),
        domain.getPublicationUrl(), domain.getSubscriptionUrl()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1186 1187 1188 1189 1190


    cmd_id += 1
    # Add or replace objects
    syncml_data = ''
1191
    # Now we have to send our own modifications
1192 1193
    if has_next_action == 0 and not \
      (domain.domain_type==self.SUB and alert_code==self.SLOW_SYNC):
1194 1195
      (syncml_data,xml_confirmation,cmd_id) = self.getSyncMLData(domain=domain,
                                       remote_xml=remote_xml,
1196 1197
                                       subscriber=subscriber,
                                       destination_path=destination_path,
1198 1199
                                       cmd_id=cmd_id,xml_confirmation=xml_confirmation,
                                       conduit=conduit)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1200 1201

    # syncml body
Sebastien Robin's avatar
Sebastien Robin committed
1202
    xml(' <SyncBody>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1203 1204 1205 1206
    destination_url = ''
    if domain.domain_type == self.PUB:
      subscriber.NewAnchor()
      destination_url = domain.getPublicationUrl()
Sebastien Robin's avatar
Sebastien Robin committed
1207
      xml(self.SyncMLStatus(cmd_id, subscriber.getSubscriptionUrl(),
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1208 1209
                               domain.getDestinationPath(),
                               subscriber.getSynchronizationType(),
Sebastien Robin's avatar
Sebastien Robin committed
1210
                               subscriber.getNextAnchor()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1211 1212
    elif domain.domain_type == self.SUB:
      destination_url = domain.getPublicationUrl()
Sebastien Robin's avatar
Sebastien Robin committed
1213
      xml(self.SyncMLStatus(cmd_id, domain.getPublicationUrl(),
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1214 1215
                               subscriber.getDestinationPath(),
                               subscriber.getSynchronizationType(),
Sebastien Robin's avatar
Sebastien Robin committed
1216
                               subscriber.getNextAnchor()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1217 1218
    # alert message if we want more data
    if destination_waiting_more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
1219
      xml(self.SyncMLAlert(cmd_id, self.WAITING_DATA,
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1220 1221
                              destination_url,
                              domain.getDestinationPath(),
Sebastien Robin's avatar
Sebastien Robin committed
1222 1223
                              subscriber.getLastAnchor(), 
                              subscriber.getNextAnchor()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1224
    # Now we should send confirmations
Sebastien Robin's avatar
Sebastien Robin committed
1225
    xml(xml_confirmation)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1226
    if syncml_data != '':
Sebastien Robin's avatar
Sebastien Robin committed
1227 1228 1229 1230 1231 1232 1233
      xml('  <Sync>\n')
      xml(syncml_data)
      xml('  </Sync>\n')
    xml('  <Final/>\n')
    xml(' </SyncBody>\n')
    xml('</SyncML>\n')
    xml_a = ''.join(xml_list)
1234
    if domain.domain_type == self.PUB: # We always reply
Sebastien Robin's avatar
Sebastien Robin committed
1235 1236 1237 1238
      subscriber.setLastSentMessage(xml_a)
      self.sendResponse(from_url=domain.publication_url, 
          to_url=subscriber.subscription_url, sync_id=domain.getTitle(), 
          xml=xml_a,domain=domain)
1239 1240 1241 1242 1243
      has_response = 1
    elif domain.domain_type == self.SUB:
      if self.checkAlert(remote_xml) or \
         (xml_confirmation,syncml_data)!=('','') or \
          has_status_list:
Sebastien Robin's avatar
Sebastien Robin committed
1244 1245 1246 1247
        subscriber.setLastSentMessage(xml_a)
        self.sendResponse(from_url=domain.subscription_url, 
            to_url=domain.publication_url, sync_id=domain.getTitle(), 
            xml=xml_a,domain=domain)
1248
        has_response = 1
Sebastien Robin's avatar
Sebastien Robin committed
1249
    return {'has_response':has_response,'xml':xml_a}