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

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

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

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

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

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

Sebastien Robin's avatar
Sebastien Robin committed
97 98
  def SyncMLAlert(self, cmd_id, sync_code, target, source, last_anchor, 
      next_anchor):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
99 100 101 102
    """
      Since the Alert section is always almost the same, this is the
      way to set one quickly.
    """
Sebastien Robin's avatar
Sebastien Robin committed
103 104 105 106 107 108 109 110 111 112 113 114 115
    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')
116
    xml('     <Anchor>\n')
Sebastien Robin's avatar
Sebastien Robin committed
117 118 119 120 121 122 123 124
    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
125

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

    #list of element in the SyncBody bloc
    syncbody_element_list = remote_xml.xpath('//SyncBody/*')
    message_id = self.getMessageId(remote_xml)
Sebastien Robin's avatar
Sebastien Robin committed
136 137
    xml_list = []
    xml = xml_list.append
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157

    if data_code != self.AUTH_REQUIRED:#because for AUTH_REQUIRED, SyncMLChal is                                       #called
      # status for SyncHdr
      message_id = self.getMessageId(remote_xml)
      xml('  <Status>\n')
      xml('   <CmdID>%s</CmdID>\n' % cmd_id)
      cmd_id += 1
      xml('   <MsgRef>%s</MsgRef>\n' % self.getMessageId(remote_xml))
      xml('   <CmdRef>0</CmdRef>\n') #to make reference to the SyncHdr, it's 0
      xml('   <Cmd>SyncHdr</Cmd>\n')
      xml('   <TargetRef>%s</TargetRef>\n' \
        % remote_xml.xpath('string(//SyncHdr/Target/LocURI)').encode('utf-8'))
      xml('   <SourceRef>%s</SourceRef>\n' \
        % remote_xml.xpath('string(//SyncHdr/Source/LocURI)').encode('utf-8'))
      if isinstance(data_code, int):
        data_code = str(data_code)
      xml('   <Data>%s</Data>\n' % data_code)
      xml('  </Status>\n')
    #add the status bloc corresponding to the receive command
    for syncbody_element in syncbody_element_list:
158
      #LOG('SyncMLStatus : ', DEBUG, "command:%s, subscription:%s" % (str(syncbody_element.nodeName), subscription))
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
      if str(syncbody_element.nodeName) not in ('Status', 'Final', 'Get'):
        xml('  <Status>\n')
        xml('   <CmdID>%s</CmdID>\n' % cmd_id)
        cmd_id += 1
        xml('   <MsgRef>%s</MsgRef>\n' % message_id)
        xml('   <CmdRef>%s</CmdRef>\n' \
            % syncbody_element.xpath('string(.//CmdID)').encode('utf-8'))
        xml('   <Cmd>%s</Cmd>\n' % syncbody_element.nodeName.encode('utf-8'))

        target_ref = syncbody_element.xpath('string(.//Target/LocURI)').encode('utf-8')
        if target_ref not in (None, ''):
          xml('   <TargetRef>%s</TargetRef>\n' % target_ref )
        source_ref = syncbody_element.xpath('string(.//Source/LocURI)').encode('utf-8')
        if source_ref not in (None, ''):
          xml('   <SourceRef>%s</SourceRef>\n' % source_ref )
        if syncbody_element.nodeName.encode('utf-8') == 'Add':
          xml('   <Data>%s</Data>\n' % str(self.ITEM_ADDED))
        elif syncbody_element.nodeName.encode('utf-8') == 'Alert' and \
            syncbody_element.xpath('string(.//Data)').encode('utf-8') == \
            str(self.SLOW_SYNC):
          xml('   <Data>%s</Data>\n' % str(self.REFRESH_REQUIRED))
        else:
          xml('   <Data>%s</Data>\n' % str(self.SUCCESS))

        if str(syncbody_element.nodeName) == 'Alert':
          xml('   <Item>\n')
          xml('    <Data>\n')
          xml('     <Anchor>\n')
          xml('      <Next>%s</Next>\n' % next_anchor)
          xml('     </Anchor>\n')
          xml('    </Data>\n')
          xml('   </Item>\n')
        xml('  </Status>\n')

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

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

Sebastien Robin's avatar
Sebastien Robin committed
219 220 221
    xml_list = []
    xml = xml_list.append
    xml('  <Status>\n')
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
    #here there is a lot of test to keep compatibility with older call
    if cmd_id not in (None,'') :
      xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    if msg_ref not in (None,''):
      xml('   <MsgRef>%s</MsgRef>\n' % msg_ref)
    if cmd_ref not in (None,''):
      xml('   <CmdRef>%s</CmdRef>\n' %cmd_ref)
    if cmd not in (None,''):
      xml('   <Cmd>%s</Cmd>\n' % cmd)
    if target_ref not in (None,''):
      xml('   <TargetRef>%s</TargetRef>\n' % target_ref)
    if source_ref not in (None,''):
      xml('   <SourceRef>%s</SourceRef>\n' % source_ref)
    if sync_code not in (None,''):
      xml('   <Data>%s</Data>\n' % sync_code)
Sebastien Robin's avatar
Sebastien Robin committed
237 238 239
    xml('  </Status>\n')
    xml_a = ''.join(xml_list)
    return xml_a
240 241

  def SyncMLChal(self, cmd_id, cmd, target_ref, source_ref, auth_format,
Sebastien Robin's avatar
Sebastien Robin committed
242 243 244 245 246 247 248 249
      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)
250 251
    xml('   <MsgRef>1</MsgRef>\n')
    xml('   <CmdRef>0</CmdRef>\n')
Sebastien Robin's avatar
Sebastien Robin committed
252 253 254 255 256
    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')
257 258
    xml("     <Format xmlns='syncml:metinf'>%s</Format>\n" % auth_format)
    xml("     <Type xmlns='syncml:metinf'>%s</Type>\n" % auth_type)
Sebastien Robin's avatar
Sebastien Robin committed
259 260 261 262 263 264
    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
265

266
  def SyncMLPut(self, cmd_id, subscription, markup='Put', cmd_ref=None,
267
      message_id=None):
268 269
    """
    this is used to inform the server of the CTType version supported
270 271
    but if the server use it to respond to a Get request, it's a <Result> markup
    instead of <Put>
272 273
    """
    conduit_name = subscription.getConduit()
274
    conduit = self.getConduitByName(conduit_name)
275
    #if the conduit support the SyncMLPut :
276
    if hasattr(conduit, 'getCapabilitiesCTTypeList') and \
277 278 279 280
        hasattr(conduit, 'getCapabilitiesVerCTList') and \
        hasattr(conduit, 'getPreferedCapabilitieVerCT'):
      xml_list = []
      xml = xml_list.append
281 282 283 284 285 286 287 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 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
      xml('  <%s>\n' % markup)
      xml('   <CmdID>%s</CmdID>\n' % cmd_id)
      if message_id not in (None, ''):
        xml('   <MsgRef>%s</MsgRef>\n' % message_id)
      if cmd_ref not in (None, '') :
        xml('   <CmdRef>%s</CmdRef>\n' % cmd_ref)
      xml('   <Meta>\n')
      xml('    <Type>application/vnd.syncml-devinf+xml</Type>\n');
      xml('   </Meta>\n')
      xml('   <Item>\n')
      xml('    <Source>\n')
      xml('     <LocURI>./devinf11</LocURI>\n')
      xml('    </Source>\n')
      xml('    <Data>\n')
      xml('     <DevInf>\n')
      xml('      <VerDTD>1.1</VerDTD>\n')
      xml('      <Man>Nexedi</Man>\n')
      xml('      <Mod>ERP5SyncML</Mod>\n')
      xml('      <OEM>Open Source</OEM>\n')
      xml('      <SwV>0.1</SwV>\n')
      xml('      <DevID>%s</DevID>\n' % subscription.getSubscriptionUrl())
      xml('      <DevTyp>workstation</DevTyp>\n')
      xml('      <UTC/>\n')
      xml('      <DataStore>\n')
      xml('       <SourceRef>%s</SourceRef>\n' % subscription.getSourceURI())
      xml('       <Rx-Pref>\n')
      xml('        <CTType>%s</CTType>\n' % \
          conduit.getPreferedCapabilitieCTType())
      xml('        <VerCT>%s</VerCT>\n' % \
          conduit.getPreferedCapabilitieVerCT())
      xml('       </Rx-Pref>\n')
      for type in conduit.getCapabilitiesCTTypeList():
        if type != self.MEDIA_TYPE['TEXT_XML']:
          for rx_version in conduit.getCapabilitiesVerCTList(type):
            xml('       <Rx>\n')
            xml('        <CTType>%s</CTType>\n' % type)
            xml('        <VerCT>%s</VerCT>\n' % rx_version)
            xml('       </Rx>\n')

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

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

      xml('      </DataStore>\n')
      xml('     </DevInf>\n')
      xml('    </Data>\n')
      xml('   </Item>\n')
      xml('  </%s>\n' % markup)
346 347 348 349 350
      xml_a = ''.join(xml_list)
      return xml_a
    return ''


Jean-Paul Smets's avatar
Jean-Paul Smets committed
351 352 353 354 355 356 357 358 359
  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
360
    #LOG('SubSendMail', DEBUG,'from: %s, to: %s' % (fromaddr,toaddr))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
361 362 363
    server = smtplib.SMTP('localhost')
    server.sendmail(fromaddr, toaddr, msg)
    # if we want to send the email to someone else (debugging)
364
    #server.sendmail(fromaddr, "seb@localhost", msg)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
365 366
    server.quit()

367
  def addXMLObject(self, cmd_id=0, object=None, xml_string=None,
368
                  more_data=0, gid=None, media_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
369 370 371
    """
      Add an object with the SyncML protocol
    """
Sebastien Robin's avatar
Sebastien Robin committed
372 373 374 375
    xml_list = []
    xml = xml_list.append
    xml('   <Add>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
376
    xml('    <Meta>\n')
377
    xml('     <Type>%s</Type>\n' % media_type)
378
    xml('    </Meta>\n')
Sebastien Robin's avatar
Sebastien Robin committed
379 380 381 382
    xml('    <Item>\n')
    xml('     <Source>\n')
    xml('      <LocURI>%s</LocURI>\n' % gid)
    xml('     </Source>\n')
383 384 385 386 387 388 389 390
    if media_type == self.MEDIA_TYPE['TEXT_XML']:
      xml('     <Data>')
      xml(xml_string)
      xml('</Data>\n')
    else:
      xml('     <Data><![CDATA[')
      xml(xml_string)
      xml('\n]]></Data>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
391
    if more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
392 393 394 395 396
      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
397

398
  def deleteXMLObject(self, cmd_id=0, object_gid=None, rid=None, xml_object=''):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
399
    """
Sebastien Robin's avatar
Sebastien Robin committed
400 401 402 403 404 405 406
      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')
407 408 409 410 411 412 413 414
    if rid not in (None, ''):
      xml('     <Target>\n')
      xml('      <LocURI>%s</LocURI>\n' % rid)
      xml('     </Target>\n')
    else:
      xml('     <Source>\n')
      xml('      <LocURI>%s</LocURI>\n' % object_gid)
      xml('     </Source>\n')
Sebastien Robin's avatar
Sebastien Robin committed
415 416 417 418
    xml('    </Item>\n')
    xml('   </Delete>\n')
    xml_a = ''.join(xml_list)
    return xml_a
Jean-Paul Smets's avatar
Jean-Paul Smets committed
419

420
  def replaceXMLObject(self, cmd_id=0, object=None, xml_string=None,
421
                       more_data=0, gid=None, rid=None, media_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
422
    """
Sebastien Robin's avatar
Sebastien Robin committed
423 424 425 426 427 428
      Replace an object with the SyncML protocol
    """
    xml_list = []
    xml = xml_list.append
    xml('   <Replace>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
429
    xml('    <Meta>\n')
430
    xml('     <Type>%s</Type>\n' % media_type)
431
    xml('    </Meta>\n')
Sebastien Robin's avatar
Sebastien Robin committed
432
    xml('    <Item>\n')
433 434 435 436 437 438 439 440
    if rid is not None:
      xml('     <Target>\n')
      xml('      <LocURI>%s</LocURI>\n' % str(rid))
      xml('     </Target>\n')
    else:
      xml('     <Source>\n')
      xml('      <LocURI>%s</LocURI>\n' % str(gid))
      xml('     </Source>\n')
441
    xml('     <Data>')
Sebastien Robin's avatar
Sebastien Robin committed
442 443
    xml(xml_string)
    xml('     </Data>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
444
    if more_data == 1:
Sebastien Robin's avatar
Sebastien Robin committed
445 446 447 448 449
      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
450

Nicolas Delaby's avatar
Nicolas Delaby committed
451
  def getXupdateObject(self, object_xml=None, old_xml=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
452 453
    """
    Generate the xupdate with the new object and the old xml
Nicolas Delaby's avatar
Nicolas Delaby committed
454 455 456 457 458 459 460 461
    """
    erp5diff = ERP5Diff()
    erp5diff.compare(old_xml, object_xml)
    xupdate_doc = erp5diff._result
    #minidom is buggy, add namespace declaration, and version
    attr_ns = xupdate_doc.createAttribute('xmlns:xupdate')
    attr_ns.value = 'http://www.xmldb.org/xupdate'
    attr_version = xupdate_doc.createAttribute('version')
462
    attr_version.value = '1.0'
Nicolas Delaby's avatar
Nicolas Delaby committed
463 464
    xupdate_doc.documentElement.setAttributeNode(attr_ns)
    xupdate_doc.documentElement.setAttributeNode(attr_version)
465
    xupdate = xupdate_doc.toxml('utf-8')
Nicolas Delaby's avatar
Nicolas Delaby committed
466
    #omit xml declaration
Jean-Paul Smets's avatar
Jean-Paul Smets committed
467 468 469
    xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
    return xupdate

470 471 472 473 474
  def getSessionId(self, xml):
    """
    We will retrieve the session id of the message
    """
    session_id = 0
475 476
    session_id = xml.xpath('string(/SyncML/SyncHdr/SessionID)')
    session_id = int(session_id)
477
    return session_id
478

Sebastien Robin's avatar
Sebastien Robin committed
479 480 481 482 483
  def getMessageId(self, xml):
    """
    We will retrieve the message id of the message
    """
    message_id = 0
484 485
    message_id = xml.xpath('string(/SyncML/SyncHdr/MsgID)')
    message_id = int(message_id)
Sebastien Robin's avatar
Sebastien Robin committed
486 487 488 489 490 491 492
    return message_id

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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
497 498 499 500 501
  def getAlertLastAnchor(self, xml_stream):
    """
      Return the value of the last anchor, in the
      alert section of the xml_stream
    """
502
    last_anchor = xml_stream.xpath('string(.//Alert/Item/Meta/Anchor/Last)')
503
    last_anchor = last_anchor.encode('utf-8')
Sebastien Robin's avatar
Sebastien Robin committed
504
    return last_anchor
Jean-Paul Smets's avatar
Jean-Paul Smets committed
505 506 507 508 509 510

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

515
  def getSourceURI(self, xml):
516 517 518 519 520 521 522
    """
    return the source uri of the data base
    """
    source_uri = xml.xpath('string(//SyncBody/Alert/Item/Source/LocURI)')
    if isinstance(source_uri, unicode):
      source_uri = source_uri.encode('utf-8')
    return source_uri
523

524 525 526 527 528 529 530 531 532
  def getTargetURI(self, xml):
    """
    return the target uri of the data base
    """
    target_uri = xml.xpath('string(//SyncBody/Alert/Item/Target/LocURI)')
    if isinstance(target_uri, unicode):
      target_uri = target_uri.encode('utf-8')
    return target_uri

533
  def getSubscriptionUrlFromXML(self, xml):
534 535 536 537 538 539 540
    """
    return the source URI of the syncml header
    """
    subscription_url = xml.xpath('string(//SyncHdr/Source/LocURI)')
    if isinstance(subscription_url, unicode):
      subscription_url = subscription_url.encode('utf-8')
    return subscription_url
541

Jean-Paul Smets's avatar
Jean-Paul Smets committed
542 543 544 545
  def getStatusTarget(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
546 547 548 549
    status = xml.xpath('string(TargetRef)')
    if isinstance(status, unicode):
      status = status.encode('utf-8')
    return status
Jean-Paul Smets's avatar
Jean-Paul Smets committed
550 551 552 553 554

  def getStatusCode(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
555 556 557
    status_code = xml.xpath('string(Data)')
    if status_code not in ('', None, []):
      return int(status_code)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
558 559
    return None

560 561 562 563
  def getStatusCommand(self, xml):
    """
      Return the value of the command inside the xml_stream
    """
564
    cmd = None
565
    if xml.nodeName=='Status':
566 567 568
      cmd = xml.xpath('string(//Status/Cmd)')
      if isinstance(cmd, unicode):
        cmd = cmd.encode('utf-8')
569
    return cmd
570

571 572 573
  def getCred(self, xml):
    """
      return the credential information : type, format and data
Nicolas Delaby's avatar
Nicolas Delaby committed
574
    """
575 576 577 578 579
    format=''
    type=''
    data=''

    first_node = xml.childNodes[0]
580 581
    format = first_node.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Format'])")
    type = first_node.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Type'])")
582
    data = first_node.xpath('string(/SyncML/SyncHdr/Cred/Data)')
583

584 585 586
    format = format.encode('utf-8')
    type = type.encode('utf-8')
    data = data.encode('utf-8')
587 588
    return (format, type, data)

589
  def checkCred(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
590
    """
591
      Check if there's a Cred section in the xml_stream
Jean-Paul Smets's avatar
Jean-Paul Smets committed
592
    """
593
    return xml_stream.xpath('string(SyncML/SyncHdr/Cred)') not in ('', None, [])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
594

595
  def getChal(self, xml):
Sebastien Robin's avatar
Sebastien Robin committed
596
    """
597
      return the chalenge information : format and type
598
    """
599 600
    format=None
    type=None
601 602

    first_node = xml.childNodes[0]
603 604
    format = first_node.xpath("string(//*[local-name() = 'Format'])")
    type = first_node.xpath("string(//*[local-name() = 'Type'])")
605 606 607 608 609 610

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

  def checkChal(self, xml_stream):
Sebastien Robin's avatar
Sebastien Robin committed
611
    """
612 613
      Check if there's a Chal section in the xml_stream
    """
614
    return xml_stream.xpath('string(SyncML/SyncBody/Status/Chal)') not in ('', None, [])
615

616 617 618 619
  def checkMap(self, xml_stream):
    """
      Check if there's a Map section in the xml_stream
    """
620
    return xml_stream.xpath('string(SyncML/SyncBody/Map)') not in ('', None, [])
621 622 623 624 625 626 627 628 629 630 631

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

634
  def getAlertCodeFromXML(self, xml_stream):
635 636 637 638 639 640 641 642
    """
      Return the value of the alert code inside the full syncml message
    """
    alert_code = xml_stream.xpath('string(SyncML/SyncBody/Alert/Data)')
    if alert_code not in (None, ''):
      return int(alert_code)
    else:
      return None
Sebastien Robin's avatar
Sebastien Robin committed
643

Jean-Paul Smets's avatar
Jean-Paul Smets committed
644 645
  def checkAlert(self, xml_stream):
    """
Sebastien Robin's avatar
Sebastien Robin committed
646
      Check if there's an Alert section in the xml_stream
Jean-Paul Smets's avatar
Jean-Paul Smets committed
647
    """
648
    return xml_stream.xpath('string(SyncML/SyncBody/Alert)') not in ('', None, [])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
649 650 651 652 653

  def checkSync(self, xml_stream):
    """
      Check if there's an Sync section in the xml_xtream
    """
654
    return xml_stream.xpath('string(SyncML/SyncBody/Sync)') not in ('', None, [])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
655

656
  def checkStatus(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
657 658 659
    """
      Check if there's a Status section in the xml_xtream
    """
660
    return xml_stream.xpath('string(SyncML/SyncBody/Status)') not in ('', None, [])
Jean-Paul Smets's avatar
Jean-Paul Smets committed
661

662
  def getSyncActionList(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
663
    """
664
    return the list of the action (could be "add", "replace", "delete").
Jean-Paul Smets's avatar
Jean-Paul Smets committed
665
    """
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
    return xml_stream.xpath('//Add|//Delete|//Replace')

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

684 685 686 687
  def getDataText(self, action):
    """
    return the section data in text form, it's usefull for the VCardConduit
    """
688 689 690 691
    data = action.xpath('string(Item/Data)')
    if isinstance(data, unicode):
      data = data.encode('utf-8')
    return data
692

Jean-Paul Smets's avatar
Jean-Paul Smets committed
693 694 695 696
  def getDataSubNode(self, action):
    """
      Return the node starting with <object....> of the action
    """
697 698 699 700
    if action.xpath('.//Item/Data') not in ([], None):
      data_node = action.xpath('.//Item/Data')[0]
      if data_node.hasChildNodes():
        return data_node.childNodes[0]
701
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
702 703 704 705 706

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

712
  def getActionId(self, action):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
713 714 715
    """
      Return the rid of the object described by the action
    """
716 717 718 719 720 721
    id = action.xpath('string(.//Item/Source/LocURI)')
    if id in (None, ''):
      id = action.xpath('string(.//Item/Target/LocURI)')
    if isinstance(id, unicode):
      id = id.encode('utf-8')
    return id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
722 723 724 725 726

  def checkActionMoreData(self, action):
    """
      Return the rid of the object described by the action
    """
727
    return action.xpath('Item/MoreData') not in ([],None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
728 729 730 731 732

  def getActionType(self, action):
    """
      Return the type of the object described by the action
    """
733 734 735 736
    action_type = action.xpath('string(Meta/Type)')
    if isinstance(action_type, unicode):
      action_type = action_type.encode('utf-8')
    return action_type
Jean-Paul Smets's avatar
Jean-Paul Smets committed
737

738 739 740 741
  def getElementNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
742
    return node.xpath('*')
743

744 745 746 747 748
  def getTextNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
    subnode_list = []
749
    for subnode in node.childNodes or []:
750 751 752
      if subnode.nodeType == subnode.TEXT_NODE:
        subnode_list += [subnode]
    return subnode_list
753

754 755 756 757
  def getAttributeNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
758
    return node.xpath('@*')
759

760 761
  def getSyncMLData(self, domain=None, remote_xml=None, cmd_id=0,
                    subscriber=None, xml_confirmation=None, conduit=None,
Nicolas Delaby's avatar
Nicolas Delaby committed
762
                    max=None, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
763
    """
764 765
    This generate the syncml data message. This returns a string
    with all modification made locally (ie replace, add ,delete...)
766 767 768

    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
769
    """
770
    #LOG('getSyncMLData starting...', DEBUG, domain.getId())
Sebastien Robin's avatar
Sebastien Robin committed
771 772
    if isinstance(conduit, str):
      conduit = self.getConduitByName(conduit)
773
    local_gid_list = []
774
    syncml_data = kw.get('syncml_data','')
Nicolas Delaby's avatar
Nicolas Delaby committed
775
    result = {'finished':1}
776 777
    if isinstance(remote_xml, str) or isinstance(remote_xml, unicode):
      remote_xml = Parse(remote_xml)
778
    if subscriber.getRemainingObjectPathList() is None:
779
      object_list = domain.getObjectList()
780
      object_path_list = [x.getPhysicalPath() for x in object_list]
781
      subscriber.setRemainingObjectPathList(object_path_list)
782 783 784 785 786 787
      if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_VCARD']:
        #here the method getGidFromObject don't return the good gid because
        #the conduit use the catalog to determine it and object are not yet
        #cataloged so if there is many times the same gid, we must count it
        gid_not_encoded_list = []
        for object in object_list:
788
          #LOG('getSyncMLData :', DEBUG, 'object:%s,  objectTitle:%s, local_gid_list:%s' % (object, object.getTitle(), local_gid_list))
789 790 791 792 793 794 795
          gid = b16decode(domain.getGidFromObject(object))
          if gid in gid_not_encoded_list:
            number = len([item for item in gid_not_encoded_list if item.startswith(gid)])
            if number > 0:
              gid = gid+'__'+str(number+1)
          gid_not_encoded_list.append(gid)
          local_gid_list.append(b16encode(gid))
796
          #LOG('getSyncMLData :', DEBUG,'gid_not_encoded_list:%s, local_gid_list:%s, gid:%s' % (gid_not_encoded_list, local_gid_list, gid))
797 798
      else:
        local_gid_list = map(lambda x: domain.getGidFromObject(x),object_list)
799 800

      # Objects to remove
801
      #LOG('getSyncMLData remove object to remove ...', DEBUG, '')
802
      object_gid_deleted = []
803
      for object_gid in subscriber.getGidList():
Nicolas Delaby's avatar
Nicolas Delaby committed
804
        if object_gid not in local_gid_list:
805
          # This is an object to remove
806
          signature = subscriber.getSignatureFromGid(object_gid)
807 808
          if signature.getStatus() != self.PARTIAL:
            # If partial, then we have a signature but no local object
809
            xml_object = signature.getXML()
810
            if xml_object is not None: # This prevent to delete an object that
811
                                       # we were not able to create
812
              rid = signature.getRid()
813
              object_gid_deleted.append(object_gid)
814 815 816 817 818
              syncml_data += self.deleteXMLObject(
                                      xml_object=signature.getXML() or '',
                                      object_gid=object_gid,
                                      rid=rid,
                                      cmd_id=cmd_id)
819
              cmd_id += 1
Nicolas Delaby's avatar
Nicolas Delaby committed
820 821 822 823
      #delete Signature if object does not exist anymore
      for known_gid in subscriber.getGidList():
        if known_gid not in local_gid_list:
          subscriber.delSignature(known_gid)
824
    local_gid_list = []
825
    loop = 0
826
    for object_path in subscriber.getRemainingObjectPathList():
Nicolas Delaby's avatar
Nicolas Delaby committed
827 828 829
      if max is not None and loop >= max:
        result['finished'] = 0
        break
830
      #LOG('getSyncMLData object_path', DEBUG, object_path)
831
      object = self.unrestrictedTraverse(object_path)
832
      status = self.SENT
833
      object_gid = domain.getGidFromObject(object)
834
      if object_gid in ('', None):
Nicolas Delaby's avatar
Nicolas Delaby committed
835
        continue
836
      local_gid_list += [object_gid]
837
      force = 0
Nicolas Delaby's avatar
Nicolas Delaby committed
838
      if syncml_data.count('\n') < self.MAX_LINES and not \
839 840
          object.id.startswith('.'):
        # If not we have to cut
841 842 843 844 845 846 847
        #LOG('getSyncMLData', DEBUG, 'object_path: %s' % '/'.join(object_path))
        #LOG('getSyncMLData', DEBUG, 'xml_mapping: %s' % str(domain.getXMLMapping()))
        #LOG('getSyncMLData', DEBUG, 'code: %s' % str(self.getAlertCodeFromXML(remote_xml)))
        #LOG('getSyncMLData', DEBUG, 'gid_list: %s' % str(local_gid_list))
        #LOG('getSyncMLData', DEBUG, 'subscriber.getGidList: %s' % subscriber.getGidList())
        #LOG('getSyncMLData', DEBUG, 'hasSignature: %s' % str(subscriber.hasSignature(object_gid)))
        #LOG('getSyncMLData', DEBUG, 'alert_code == slowsync: %s' % str(self.getAlertCodeFromXML(remote_xml) == self.SLOW_SYNC))
848

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

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

1202
        elif action.nodeName == 'Delete':
1203
          object_id = signature.getId()
1204
          #LOG('applyActionList Delete on : ', DEBUG, (signature.getId(), subscriber.getObjectFromGid(object_id)))
1205
          if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
1206
            data_subnode = self.getDataText(action)
1207
          else:
1208
            data_subnode = self.getDataSubNode(action)
1209
          #LOG('applyActionList, object gid to delete :', 0, subscriber.getObjectFromGid(object_id))
1210 1211
          if subscriber.getObjectFromGid(object_id) not in (None, ''):
          #if the object exist:
1212 1213 1214 1215
            conduit.deleteNode(
                        xml=data_subnode,
                        object=destination,
                        object_id=subscriber.getObjectFromGid(object_id).getId())
1216 1217
            subscriber.delSignature(gid)
          xml_confirmation += self.SyncMLConfirmation(
1218 1219 1220 1221
                                            cmd_id=cmd_id,
                                            cmd='Delete',
                                            sync_code=status_code,
                                            remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1222 1223 1224
      else: # We want to retrieve more data
        signature.setStatus(self.PARTIAL)
        previous_partial = signature.getPartialXML() or ''
1225
        previous_partial += partial_data
1226
        #LOG('applyActionList', DEBUG, 'setPartialXML: %s' % str(previous_partial))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1227
        signature.setPartialXML(previous_partial)
1228 1229
        #LOG('applyActionList', DEBUG, 'previous_partial: %s' % str(previous_partial))
        #LOG('applyActionList', DEBUG, 'waiting more data for :%s' % signature.getId())
1230 1231 1232 1233 1234
        xml_confirmation += self.SyncMLConfirmation(
                                          cmd_id=cmd_id,
                                          cmd=action.nodeName,
                                          sync_code=self.WAITING_DATA,
                                          remote_xml=action)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1235 1236 1237
      if conflict_list != [] and signature is not None:
        # We had a conflict
        signature.setStatus(self.CONFLICT)
1238

1239
    return (xml_confirmation, has_next_action, cmd_id)
1240

1241
  def applyStatusList(self, subscriber=None, remote_xml=None):
1242 1243 1244 1245
    """
    This read a list of status list (ie syncml confirmations).
    This method have to change status codes on signatures
    """
1246
    status_list = self.getSyncBodyStatusList(remote_xml)
1247 1248
    has_status_list = 0
    destination_waiting_more_data = 0
1249 1250 1251 1252
    if status_list != []:
      for status in status_list:
        status_cmd = status['cmd']
        object_gid = status['source']
1253 1254
        if object_gid in ('', None, []):
          object_gid = status['target']
1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276
        status_code = int(status['code'])
        if status_cmd in ('Add','Replace'):
          has_status_list = 1
          signature = subscriber.getSignatureFromGid(object_gid)
          if signature == None:
            signature = subscriber.getSignatureFromRid(object_gid)
          if status_code == self.CHUNK_OK:
            destination_waiting_more_data = 1
            signature.setStatus(self.PARTIAL)
          elif status_code == self.CONFLICT:
            signature.setStatus(self.CONFLICT)
          elif status_code == self.CONFLICT_MERGE:
            # We will have to apply the update, and we should not care 
            # about conflicts, so we have to force the update
            signature.setStatus(self.NOT_SYNCHRONIZED)
            signature.setForce(1)
          elif status_code == self.CONFLICT_CLIENT_WIN:
            # The server was agree to apply our updates, nothing to do
            signature.setStatus(self.SYNCHRONIZED)
          elif status_code in (self.SUCCESS, self.ITEM_ADDED):
            signature.setStatus(self.SYNCHRONIZED)
        elif status_cmd == 'Delete':
Fabien Morin's avatar
Fabien Morin committed
1277
          has_status_list = 1
1278
          if status_code == self.SUCCESS:
1279 1280 1281 1282
            signature = subscriber.getSignatureFromGid(object_gid)
            if signature is None and \
            not(subscriber.getSynchronizeWithERP5Sites()):
              signature = subscriber.getSignatureFromRid(object_gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
1283 1284
            if signature is not None:
              subscriber.delSignature(signature.getGid())
1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302
    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

1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321
  def getConduitByName(self, conduit_name):
    """
    Get Conduit Object by given name.
    The Conduit can be located in Any Products according to naming Convention
    Products.<Product Name>.Conduit.<Conduit Module> ,if conduit_name equal module's name.
    By default Conduit must be defined in Products.ERP5SyncML.Conduit.<Conduit Module>
    """
    from Products.ERP5SyncML import Conduit
    if conduit_name.startswith('Products'):
      path = conduit_name
      conduit_name = conduit_name.split('.')[-1]
      conduit_module = __import__(path, globals(), locals(), [''])
      conduit = getattr(conduit_module, conduit_name)()
    else:
      conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]),
                                  globals(), locals(), [''])
      conduit = getattr(conduit_module, conduit_name)()
    return conduit

1322 1323 1324 1325
  def SyncModif(self, domain, remote_xml):
    """
    Modification Message, this is used after the first
    message in order to send modifications.
1326 1327
    Send the server modification, this happens after the Synchronization
    initialization
1328
    """
1329
    has_response = 0 #check if syncmodif replies to this messages
1330
    cmd_id = 1 # specifies a SyncML message-unique command identifier
1331
    #LOG('SyncModif', DEBUG, 'Starting... domain: %s' % domain.getId())
Sebastien Robin's avatar
Sebastien Robin committed
1332
    first_node = remote_xml.childNodes[0]
1333 1334 1335
    # Get informations from the header
    xml_header = first_node.childNodes[1]
    if xml_header.nodeName != "SyncHdr":
1336
      LOG('SyncModif', INFO, 'This is not a SyncML Header')
Sebastien Robin's avatar
Sebastien Robin committed
1337
      raise ValueError, "Sorry, This is not a SyncML Header"
1338 1339

    subscriber = domain # If we are the client, this is fine
1340
    simulate = 0 # used by applyActionList, should be 0 for client
1341
    if domain.domain_type == self.PUB:
1342
      simulate = 1
1343
      subscription_url = self.getSubscriptionUrlFromXML(xml_header)
1344
      subscriber = domain.getSubscriber(subscription_url)
1345

1346 1347
    # 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
1348 1349 1350
    message_id = self.getMessageId(remote_xml)
    correct_message = subscriber.checkCorrectRemoteMessageId(message_id)
    if not correct_message: # We need to send again the message
1351
      LOG('SyncModif, no correct message:', INFO, "sending again...")
1352
      last_xml = subscriber.getLastSentMessage()
1353
      LOG("SyncModif last_xml :", INFO, last_xml)
1354 1355 1356
      if last_xml != '':
        has_response = 1
        if domain.domain_type == self.PUB: # We always reply
1357 1358 1359 1360 1361 1362
          self.sendResponse(
                    from_url=domain.publication_url,
                    to_url=subscriber.subscription_url,
                    sync_id=domain.getTitle(),
                    xml=last_xml,domain=domain,
                    content_type=domain.getSyncContentType())
1363
        elif domain.domain_type == self.SUB:
1364 1365 1366 1367 1368 1369 1370 1371
          self.sendResponse(
                    from_url=domain.subscription_url,
                    to_url=domain.publication_url,
                    sync_id=domain.getTitle(),
                    xml=last_xml,
                    domain=domain,
                    content_type=domain.getSyncContentType())
      return {'has_response':has_response, 'xml':last_xml}
1372 1373
    subscriber.setLastSentMessage('')

1374 1375
    # First apply the list of status codes
    (destination_waiting_more_data,has_status_list) = self.applyStatusList(
1376 1377
                                                      subscriber=subscriber,
                                                      remote_xml=remote_xml)
1378

1379
    alert_code = self.getAlertCodeFromXML(remote_xml)
Kevin Deldycke's avatar
Kevin Deldycke committed
1380
    # Import the conduit and get it
1381
    conduit = self.getConduitByName(subscriber.getConduit())
1382
    # Then apply the list of actions
1383
    (xml_confirmation, has_next_action, cmd_id) = self.applyActionList(
1384 1385 1386 1387 1388
                                          cmd_id=cmd_id,
                                          domain=domain,
                                          subscriber=subscriber,
                                          remote_xml=remote_xml,
                                          conduit=conduit, simulate=simulate)
Sebastien Robin's avatar
Sebastien Robin committed
1389 1390 1391
    xml_list = []
    xml = xml_list.append
    xml('<SyncML>\n')
1392

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1393 1394
    # syncml header
    if domain.domain_type == self.PUB:
1395 1396 1397 1398 1399
      xml(self.SyncMLHeader(
                  subscriber.getSessionId(),
                  subscriber.incrementMessageId(),
                  subscriber.getSubscriptionUrl(),
                  domain.getPublicationUrl()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1400
    elif domain.domain_type == self.SUB:
1401 1402 1403 1404
      xml(self.SyncMLHeader(
                  domain.getSessionId(), domain.incrementMessageId(),
                  domain.getPublicationUrl(),
                  domain.getSubscriptionUrl()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1405 1406 1407 1408
    # Add or replace objects
    syncml_data = ''

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

1411 1412 1413 1414 1415 1416
    xml_status, cmd_id = self.SyncMLStatus(
                                    remote_xml,
                                    self.SUCCESS,
                                    cmd_id,
                                    subscriber.getNextAnchor(),
                                    subscription=subscriber).values()
1417
    xml(xml_status)
1418

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1419 1420 1421
    destination_url = ''
    # alert message if we want more data
    if destination_waiting_more_data == 1:
1422 1423 1424 1425 1426 1427 1428
      xml(self.SyncMLAlert(
                    cmd_id,
                    self.WAITING_DATA,
                    subscriber.getTargetURI(),
                    subscriber.getSourceURI(),
                    subscriber.getLastAnchor(),
                    subscriber.getNextAnchor()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1429
    # Now we should send confirmations
1430
    cmd_id_before_getsyncmldata = cmd_id
1431
    cmd_id = cmd_id+1
1432
    if domain.getActivityEnabled():
1433 1434 1435 1436 1437
      #use activities to get SyncML data.
      if not (isinstance(remote_xml, str) or isinstance(remote_xml, unicode)):
        string_io = StringIO()
        PrettyPrint(remote_xml,stream=string_io)
        remote_xml = string_io.getvalue()
1438
      domain.activate(activity='SQLQueue').SyncModifActivity(
1439 1440 1441 1442 1443 1444 1445 1446 1447 1448
                      domain_relative_url = domain.getRelativeUrl(),
                      remote_xml = remote_xml,
                      subscriber_relative_url = subscriber.getRelativeUrl(),
                      cmd_id = cmd_id,
                      xml_confirmation = xml_confirmation,
                      syncml_data = '',
                      cmd_id_before_getsyncmldata = cmd_id_before_getsyncmldata,
                      xml_list = xml_list,
                      has_status_list = has_status_list,
                      has_response = has_response )
Nicolas Delaby's avatar
Nicolas Delaby committed
1449
      return {'has_response':1, 'xml':''}
1450 1451
    else:
      result = self.getSyncMLData(domain=domain,
1452 1453
                             remote_xml=remote_xml,
                             subscriber=subscriber,
1454
                             cmd_id=cmd_id,xml_confirmation=xml_confirmation,
1455
                             conduit=conduit,
1456
                             max=None)
1457 1458 1459 1460 1461
      syncml_data = result['syncml_data']
      xml_confirmation = result['xml_confirmation']
      cmd_id = result['cmd_id']
      return self.sendSyncModif(syncml_data, cmd_id_before_getsyncmldata,
                                subscriber, domain, xml_confirmation,
1462
                                remote_xml, xml_list, has_status_list,
1463
                                has_response)
1464 1465 1466 1467 1468

  def SyncModifActivity(self, **kw):
    domain = self.unrestrictedTraverse(kw['domain_relative_url'])
    subscriber = self.unrestrictedTraverse(kw['subscriber_relative_url'])
    conduit = subscriber.getConduit()
1469 1470 1471 1472 1473 1474
    result = self.getSyncMLData(
                        domain = domain,
                        subscriber = subscriber,
                        conduit = conduit,
                        max = self.MAX_OBJECTS,
                        **kw)
1475
    syncml_data = result['syncml_data']
1476
    cmd_id = result['cmd_id']
1477
    kw['syncml_data'] = syncml_data
1478
    kw['cmd_id'] = cmd_id
1479 1480
    finished = result['finished']
    if not finished:
1481
      domain.activate(activity='SQLQueue').SyncModifActivity(**kw)
1482 1483 1484 1485 1486 1487 1488 1489
    else:
      xml_confirmation = result['xml_confirmation']
      cmd_id = result['cmd_id']
      cmd_id_before_getsyncmldata = kw['cmd_id_before_getsyncmldata']
      remote_xml = Parse(kw['remote_xml'])
      xml_list = kw['xml_list']
      has_status_list = kw['has_status_list']
      has_response = kw['has_response']
1490 1491 1492 1493 1494 1495 1496 1497 1498 1499
      return self.sendSyncModif(
                        syncml_data,
                        cmd_id_before_getsyncmldata,
                        subscriber,
                        domain,
                        xml_confirmation,
                        remote_xml,
                        xml_list,
                        has_status_list,
                        has_response)
1500 1501 1502 1503 1504

  def sendSyncModif(self, syncml_data, cmd_id_before_getsyncmldata, subscriber,
                    domain, xml_confirmation, remote_xml, xml_list,
                    has_status_list, has_response):
    xml = xml_list.append
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1505
    if syncml_data != '':
Sebastien Robin's avatar
Sebastien Robin committed
1506
      xml('  <Sync>\n')
1507
      xml('   <CmdID>%s</CmdID>\n' % cmd_id_before_getsyncmldata)
1508 1509 1510 1511 1512 1513 1514 1515
      if subscriber.getTargetURI() not in ('', None):
        xml('   <Target>\n')
        xml('    <LocURI>%s</LocURI>\n' % subscriber.getTargetURI())
        xml('   </Target>\n')
      if subscriber.getSourceURI() not in ('', None):
        xml('   <Source>\n')
        xml('    <LocURI>%s</LocURI>\n' % subscriber.getSourceURI())
        xml('   </Source>\n')
Sebastien Robin's avatar
Sebastien Robin committed
1516 1517
      xml(syncml_data)
      xml('  </Sync>\n')
1518
    xml(xml_confirmation)
Sebastien Robin's avatar
Sebastien Robin committed
1519 1520 1521 1522
    xml('  <Final/>\n')
    xml(' </SyncBody>\n')
    xml('</SyncML>\n')
    xml_a = ''.join(xml_list)
1523

1524
    if domain.domain_type == self.PUB: # We always reply
Sebastien Robin's avatar
Sebastien Robin committed
1525
      subscriber.setLastSentMessage(xml_a)
1526 1527 1528 1529 1530 1531 1532
      self.sendResponse(
                from_url=domain.publication_url,
                to_url=subscriber.subscription_url,
                sync_id=domain.getTitle(),
                xml=xml_a,
                domain=domain,
                content_type=domain.getSyncContentType())
Fabien Morin's avatar
Fabien Morin committed
1533
      if syncml_data == '':
1534
        LOG('this is the end of the synchronisation session !!!', INFO, '')
Fabien Morin's avatar
Fabien Morin committed
1535 1536
        subscriber.setAuthenticated(False)
        domain.setAuthenticated(False)
1537 1538 1539
      has_response = 1
    elif domain.domain_type == self.SUB:
      if self.checkAlert(remote_xml) or \
Fabien Morin's avatar
Fabien Morin committed
1540
          (xml_confirmation,syncml_data) != ('',''):
Sebastien Robin's avatar
Sebastien Robin committed
1541
        subscriber.setLastSentMessage(xml_a)
1542 1543 1544 1545 1546 1547
        self.sendResponse(
                  from_url=domain.subscription_url,
                  to_url=domain.publication_url,
                  sync_id=domain.getTitle(),
                  xml=xml_a,domain=domain,
                  content_type=domain.getSyncContentType())
1548
        has_response = 1
Fabien Morin's avatar
Fabien Morin committed
1549
      else:
1550
        LOG('this is the end of the synchronisation session !!!', INFO, '')
Fabien Morin's avatar
Fabien Morin committed
1551
        domain.setAuthenticated(False)
Sebastien Robin's avatar
Sebastien Robin committed
1552
    return {'has_response':has_response,'xml':xml_a}
1553 1554 1555 1556 1557

  def xml2wbxml(self, xml):
    """
    convert xml string to wbxml using a temporary file
    """
1558
    #LOG('xml2wbxml starting ...', DEBUG, '')
1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572
    import os
    f = open('/tmp/xml2wbxml', 'w')
    f.write(xml)
    f.close()
    os.system('/usr/bin/xml2wbxml -o /tmp/xml2wbxml /tmp/xml2wbxml')
    f = open('/tmp/xml2wbxml', 'r')
    wbxml = f.read()
    f.close()
    return wbxml

  def wbxml2xml(self, wbxml):
    """
    convert wbxml string to xml using a temporary file
    """
1573
    #LOG('wbxml2xml starting ...', DEBUG, '')
1574 1575 1576 1577 1578 1579 1580 1581 1582
    import os
    f = open('/tmp/wbxml2xml', 'w')
    f.write(wbxml)
    f.close()
    os.system('/usr/bin/wbxml2xml -o /tmp/wbxml2xml /tmp/wbxml2xml')
    f = open('/tmp/wbxml2xml', 'r')
    xml = f.read()
    f.close()
    return xml
1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702

  def PubSync(self, publication_path, msg=None, RESPONSE=None, subscriber=None):
    """
      This is the synchronization method for the server
    """
    from Products.ERP5SyncML.Publication import Subscriber
    #LOG('PubSync', DEBUG, 'Starting... publication: %s' % (publication_path))
    # Read the request from the client
    publication = self.unrestrictedTraverse(publication_path)
    xml_client = msg
    if xml_client is None:
      xml_client = self.readResponse(from_url=publication.getPublicationUrl())
    #LOG('PubSync', DEBUG, 'Starting... msg: %s' % str(xml_client))
    result = None

    if xml_client is not None:
      if isinstance(xml_client, str) or isinstance(xml_client, unicode):
        xml_client = Parse(xml_client)
      first_node = xml_client.childNodes[0]

      if first_node.nodeName != "SyncML":
        LOG('PubSync', INFO, 'This is not a SyncML Message')
        raise ValueError, "Sorry, This is not a SyncML Message"
      alert_code = self.getAlertCodeFromXML(xml_client)
      # Get informations from the header
      client_header = first_node.childNodes[1]
      if client_header.nodeName != "SyncHdr":
        LOG('PubSync', INFO, 'This is not a SyncML Header')
        raise ValueError, "Sorry, This is not a SyncML Header"
      subscription_url = self.getSubscriptionUrlFromXML(client_header)
      # Get the subscriber or create it if not already in the list
      subscriber = publication.getSubscriber(subscription_url)
      if subscriber == None:
        subscriber = Subscriber(publication.generateNewId(),subscription_url)
        subscriber.setXMLMapping(publication.getXMLMapping())
        subscriber.setConduit(publication.getConduit())
        publication.addSubscriber(subscriber)
        # first synchronization
        result = self.PubSyncInit(publication,xml_client,subscriber=subscriber,
            sync_type=self.SLOW_SYNC)
      elif self.checkAlert(xml_client) and \
          alert_code in (self.TWO_WAY, self.SLOW_SYNC, \
          self.ONE_WAY_FROM_SERVER):
        subscriber.setXMLMapping(publication.getXMLMapping())
        subscriber.setConduit(publication.getConduit())
        result = self.PubSyncInit(publication=publication, 
            xml_client=xml_client, subscriber=subscriber, sync_type=alert_code)
      else:
        #we log the user authenticated to do the synchronization with him
        if self.checkMap(xml_client) :
          self.setRidWithMap(xml_client, subscriber)
        if subscriber.isAuthenticated():
            uf = self.getPortalObject().acl_users
            user = uf.getUserById(subscriber.getUser()).__of__(uf)
            newSecurityManager(None, user)
            result = self.PubSyncModif(publication, xml_client)
        else:
          result = self.PubSyncModif(publication, xml_client)
    elif subscriber is not None:
      # This looks like we are starting a synchronization after
      # a conflict resolution by the user
      result = self.PubSyncInit(publication=publication, xml_client=None,
          subscriber=subscriber, sync_type=self.TWO_WAY)

    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')
    elif result is not None:
      return result

  def SubSync(self, subscription_path, msg=None, RESPONSE=None):
    """
      This is the synchronization method for the client
    """
    response = None #check if subsync replies to this messages
    subscription = self.unrestrictedTraverse(subscription_path)
    if msg==None and (subscription.getSubscriptionUrl()).find('file')>=0:
      msg = self.readResponse(sync_id=subscription.getSubscriptionUrl(),
          from_url=subscription.getSubscriptionUrl())
    if msg==None:
      response = self.SubSyncInit(subscription)
    else:
      xml_client = msg
      if isinstance(xml_client, str) or isinstance(xml_client, unicode):
        xml_client = Parse(xml_client)
        status_list = self.getSyncBodyStatusList(xml_client)
        if status_list not in (None, []):
          status_code_syncHdr = status_list[0]['code']
          if status_code_syncHdr.isdigit():
            status_code_syncHdr = int(status_code_syncHdr)
          #LOG('SubSync status code : ', DEBUG, status_code_syncHdr)
          if status_code_syncHdr == self.AUTH_REQUIRED:
            if self.checkChal(xml_client):
              authentication_format, authentication_type = self.getChal(xml_client)
              #LOG('SubSync auth_required :', DEBUG, 'format:%s, type:%s' % (authentication_format, authentication_type))
              if authentication_format is not None and \
                  authentication_type is not None:
                subscription.setAuthenticationFormat(authentication_format)
                subscription.setAuthenticationType(authentication_type)
            else:
              raise ValueError, "Sorry, the server chalenge for an \
                  authentication, but the authentication format is not find"

            LOG('SubSync', INFO, 'Authentication required')
            response = self.SubSyncCred(subscription, xml_client)
          elif status_code_syncHdr == self.UNAUTHORIZED:
            LOG('SubSync', INFO, 'Bad authentication')
            return {'has_response':0, 'xml':xml_client}
          else:
            response = self.SubSyncModif(subscription, xml_client)
        else:
            response = self.SubSyncModif(subscription, xml_client)

    if RESPONSE is not None:
      RESPONSE.redirect('manageSubscriptions')
    else:
      return response

  def getActivityType(self, domain):
    if domain.getActivityEnabled():
      return 'SQLDict'
1703
    return 'RAMQueue'