XMLSyncUtils.py 65.7 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
Nicolas Delaby's avatar
Nicolas Delaby committed
33
from ERP5Diff import ERP5Diff
Nicolas Delaby's avatar
Nicolas Delaby committed
34
from zLOG import LOG, INFO
Nicolas Delaby's avatar
Nicolas Delaby committed
35

36
from lxml import etree
Nicolas Delaby's avatar
Nicolas Delaby committed
37
from lxml.etree import Element, SubElement
38
from lxml.builder import E
39 40
parser = etree.XMLParser(remove_blank_text=True)

41
from xml.dom import minidom
Jean-Paul Smets's avatar
Jean-Paul Smets committed
42

43
try:
44
  from base64 import b16encode, b16decode
45
except ImportError:
46
  from base64 import encodestring as b16encode, decodestring as b16decode
47

48
class XMLSyncUtilsMixin(SyncCode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
49

50 51
  def SyncMLHeader(self, session_id, msg_id, target, source, target_name=None,
      source_name=None, dataCred=None, authentication_format='b64',
52
      authentication_type='syncml:auth-basic'):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
53 54 55 56
    """
      Since the Header is always almost the same, this is the
      way to set one quickly.
    """
57 58 59 60 61 62
    xml = (E.SyncHdr(
            E.VerDTD('1.1'),
            E.VerProto('SyncML/1.1'),
            E.SessionID('%s' % session_id),
            E.MsgID('%s' % msg_id),
          ))
63 64 65 66 67 68 69 70
    target_node = E.Target(E.LocURI(target))
    if target_name:
      target_node.append(E.LocName(target_name.decode('utf-8')))
    xml.append(target_node)
    source_node = E.Source(E.LocURI(source))
    if source_name:
      source_node.append(E.LocName(source_name.decode('utf-8')))
    xml.append(source_node)
71 72 73 74 75 76
    if dataCred:
      xml.append(E.Cred(
                  E.Meta(E.Format(authentication_format, xmlns='syncml:metinf'),
                  E.Type(authentication_type, xmlns='syncml:metinf'),),
                  E.Data(dataCred)
                  ))
Nicolas Delaby's avatar
Nicolas Delaby committed
77
    return xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
78

Sebastien Robin's avatar
Sebastien Robin committed
79 80
  def SyncMLAlert(self, cmd_id, sync_code, target, source, last_anchor, 
      next_anchor):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
81 82 83 84
    """
      Since the Alert section is always almost the same, this is the
      way to set one quickly.
    """
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
    xml = (E.Alert(
            E.CmdID('%s' % cmd_id),
            E.Data('%s' % sync_code),
            E.Item(
              E.Target(
                E.LocURI(target)
                ),
              E.Source(
                E.LocURI(source)
                ),
              E.Meta(
                E.Anchor(
                  E.Last(last_anchor),
                  E.Next(next_anchor)
                  )
                )
              )
            ))
Nicolas Delaby's avatar
Nicolas Delaby committed
103
    return xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
104

Nicolas Delaby's avatar
Nicolas Delaby committed
105 106
  def SyncMLStatus(self, remote_xml, data_code, cmd_id, next_anchor,
                   subscription=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
107
    """
108 109
    return a status bloc with all status corresponding to the syncml
    commands in remote_xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
110
    """
111 112

    #list of element in the SyncBody bloc
113 114
    sub_syncbody_element_list = remote_xml.xpath('/SyncML/SyncBody/*')
    message_id = self.getMessageIdFromXml(remote_xml)
Nicolas Delaby's avatar
Nicolas Delaby committed
115 116 117 118 119 120 121 122 123 124 125 126 127
    status_list = []
    target_uri = '%s' % remote_xml.xpath('string(/SyncML/SyncHdr/Target/LocURI)')
    source_uri = '%s' % remote_xml.xpath('string(/SyncML/SyncHdr/Source/LocURI)')
    if data_code != self.AUTH_REQUIRED:
      xml = (E.Status(
               E.CmdID('%s' % cmd_id),
               E.MsgRef('%s' % message_id),
               E.CmdRef('0'),
               E.Cmd('SyncHdr'),
               E.TargetRef(target_uri),
               E.SourceRef(source_uri),
               E.Data('%s' % data_code),
               ))
128
      cmd_id += 1
Nicolas Delaby's avatar
Nicolas Delaby committed
129
      status_list.append(xml)
130 131
    for sub_syncbody_element in sub_syncbody_element_list:
      if sub_syncbody_element.tag not in ('Status', 'Final', 'Get'):
Nicolas Delaby's avatar
Nicolas Delaby committed
132 133 134 135 136 137
        xml = (E.Status(
                 E.CmdID('%s' % cmd_id),
                 E.MsgRef('%s' % message_id),
                 E.CmdRef('%s' % sub_syncbody_element.xpath('string(.//CmdID)')),
                 E.Cmd(sub_syncbody_element.tag)
                 ))
138
        cmd_id += 1
139 140
        target_ref = sub_syncbody_element.xpath('string(.//Target/LocURI)')
        if target_ref:
Nicolas Delaby's avatar
Nicolas Delaby committed
141
          xml.append(E.TargetRef('%s' % target_ref))
142 143
        source_ref = sub_syncbody_element.xpath('string(.//Source/LocURI)')
        if source_ref:
Nicolas Delaby's avatar
Nicolas Delaby committed
144
          xml.append(E.SourceRef('%s' % source_ref))
145
        if sub_syncbody_element.tag == 'Add':
Nicolas Delaby's avatar
Nicolas Delaby committed
146
          xml.append(E.Data('%s' % self.ITEM_ADDED))
147 148
        elif sub_syncbody_element.tag == 'Alert' and \
            sub_syncbody_element.xpath('string(.//Data)') == \
149
            str(self.SLOW_SYNC):
Nicolas Delaby's avatar
Nicolas Delaby committed
150
          xml.append(E.Data('%s' % self.REFRESH_REQUIRED))
151
        elif sub_syncbody_element.tag == 'Alert':
Nicolas Delaby's avatar
Nicolas Delaby committed
152
          xml.append(E.Item(E.Data(E.Anchor(E.Next(next_anchor)))))
153
        else:
Nicolas Delaby's avatar
Nicolas Delaby committed
154 155
          xml.append(E.Data('%s' % self.SUCCESS))
        status_list.append(xml)
156

157 158
      if sub_syncbody_element.tag == 'Get' and subscription is not None:
        cmd_ref = '%s' % sub_syncbody_element.xpath('string(.//CmdID)')
159 160 161 162 163
        syncml_result = self.SyncMLPut(
                                  cmd_id,
                                  subscription,
                                  markup='Results',
                                  cmd_ref=cmd_ref,
164
                                  message_id=message_id)
Nicolas Delaby's avatar
Nicolas Delaby committed
165 166
        if syncml_result is not None:
          status_list.append(syncml_result)
167
        cmd_id += 1
Nicolas Delaby's avatar
Nicolas Delaby committed
168 169

    return status_list, cmd_id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
170

171 172
  def SyncMLConfirmation(self, cmd_id=None, target_ref=None, cmd=None,
      sync_code=None, msg_ref=None, cmd_ref=None, source_ref=None,
173
      remote_xml=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
174
    """
Sebastien Robin's avatar
Sebastien Robin committed
175
    This is used in order to confirm that an object was correctly
Jean-Paul Smets's avatar
Jean-Paul Smets committed
176 177
    synchronized
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
178
    if remote_xml is not None:
179 180 181 182
      msg_ref = '%s' % remote_xml.xpath("string(/SyncML/SyncHdr/MsgID)")
      cmd_ref = '%s' % remote_xml.xpath("string(.//CmdID)")
      target_ref = '%s' % remote_xml.xpath("string(.//Target/LocURI)")
      source_ref = '%s' % remote_xml.xpath("string(.//Source/LocURI)")
Nicolas Delaby's avatar
Nicolas Delaby committed
183
    xml = Element('Status')
184 185 186 187 188 189 190 191 192 193 194 195 196 197
    if cmd_id:
      xml.append(E.CmdID('%s' % cmd_id))
    if msg_ref:
      xml.append(E.MsgRef(msg_ref))
    if cmd_ref:
      xml.append(E.CmdRef(cmd_ref))
    if cmd:
      xml.append(E.Cmd(cmd))
    if target_ref:
      xml.append(E.TargetRef(target_ref))
    if source_ref:
      xml.append(E.SourceRef(source_ref))
    if sync_code:
      xml.append(E.Data('%s'% sync_code))
Nicolas Delaby's avatar
Nicolas Delaby committed
198
    return xml
199 200

  def SyncMLChal(self, cmd_id, cmd, target_ref, source_ref, auth_format,
Nicolas Delaby's avatar
Nicolas Delaby committed
201
      auth_type, auth_code):
Sebastien Robin's avatar
Sebastien Robin committed
202 203 204
    """
    This is used in order to ask crendentials
    """
205 206 207 208 209 210 211 212 213 214 215 216 217
    xml = (E.Status(
             E.CmdID('%s' % cmd_id),
             E.MsgRef('1'),
             E.CmdRef('0'),
             E.Cmd(cmd),
             E.TargetRef(target_ref),
             E.SourceRef(source_ref),
             E.Chal(
               E.Meta(
                 E.Format(auth_format, xmlns='syncml:metinf'),
                 E.Type(auth_type, xmlns='syncml:metinf')
                 )
               ),
Nicolas Delaby's avatar
Nicolas Delaby committed
218
            E.Data('%s' % auth_code)
219
            ))
Nicolas Delaby's avatar
Nicolas Delaby committed
220
    return xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
221

222
  def SyncMLPut(self, cmd_id, subscription, markup='Put', cmd_ref=None,
223
      message_id=None):
224 225
    """
    this is used to inform the server of the CTType version supported
226 227
    but if the server use it to respond to a Get request, it's a <Result> markup
    instead of <Put>
228 229
    """
    conduit_name = subscription.getConduit()
230
    conduit = self.getConduitByName(conduit_name)
Nicolas Delaby's avatar
Nicolas Delaby committed
231
    xml = None
232
    #if the conduit support the SyncMLPut :
233 234 235
    if getattr(conduit, 'getCapabilitiesCTTypeList', None) is not None and \
       getattr(conduit, 'getCapabilitiesVerCTList', None) is not None and \
       getattr(conduit, 'getPreferedCapabilitieVerCT', None) is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
236 237
      xml = Element(markup)
      xml.append(E.CmdID('%s' % cmd_id))
238
      if message_id:
Nicolas Delaby's avatar
Nicolas Delaby committed
239
        xml.append(E.MsgRef('%s' % message_id))
240
      if cmd_ref:
Nicolas Delaby's avatar
Nicolas Delaby committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
        xml.append(E.CmdRef('%s' % cmd_ref))
      xml.append(E.Meta(E.Type('application/vnd.syncml-devinf+xml')),
                 E.Item(E.Source(E.LocURI('./devinf11')),
                 E.Data(E.DevInf(
                   E.VerDTD('1.1'),
                   E.Man('Nexedi'),
                   E.Mod('ERP5SyncML'),
                   E.OEM('Open Source'),
                   E.SwV('0.1'),
                   E.DevID(subscription.getSubscriptionUrl()),
                   E.DevTyp('workstation'),
                   E.UTC(),
                   E.DataStore(
                     E.SourceRef(subscription.getSourceURI()),
                     Element('E.Rx-Pref').append(
                       (E.CTType(conduit.getPreferedCapabilitieCTType()),
                        E.VerCT(conduit.getPreferedCapabilitieVerCT()))
                       )
                     )
                   )
                 )
               ))
      data_store = xml.find('DataStore')
      tx_element_list = []
      rx_element_list = []
266 267 268
      for type in conduit.getCapabilitiesCTTypeList():
        if type != self.MEDIA_TYPE['TEXT_XML']:
          for tx_version in conduit.getCapabilitiesVerCTList(type):
Nicolas Delaby's avatar
Nicolas Delaby committed
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
            rx_element_list.append(E.Rx(E.CTType(type), E.VerCT(rx_version)))
            tx_element_list.append(E.Tx(E.CTType(type), E.VerCT(rx_version)))
      data_store.extend(rx_element_list)
      data_store.append(Element('Tx-Pref').extend(
                           (E.CTType(conduit.getPreferedCapabilitieCTType()),
                            E.VerCT(conduit.getPreferedCapabilitieVerCT()))
                            ))
      data_store.extend(tx_element_list)
      data_store.append(E.SyncCap(
                          E.SyncType('2'),
                          E.SyncType('1'),
                          E.SyncType('4'),
                          E.SyncType('6')
                          ))
    return xml
284

Jean-Paul Smets's avatar
Jean-Paul Smets committed
285 286 287 288 289 290 291 292 293
  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
294
    #LOG('SubSendMail', DEBUG,'from: %s, to: %s' % (fromaddr,toaddr))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
295 296 297
    server = smtplib.SMTP('localhost')
    server.sendmail(fromaddr, toaddr, msg)
    # if we want to send the email to someone else (debugging)
298
    #server.sendmail(fromaddr, "seb@localhost", msg)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
299 300
    server.quit()

301
  def addXMLObject(self, cmd_id=0, object=None, xml_string=None,
302
                  more_data=0, gid=None, media_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
303 304 305
    """
      Add an object with the SyncML protocol
    """
306 307
    data_node = Element('Data')
    if media_type == self.MEDIA_TYPE['TEXT_XML'] and isinstance(xml_string, str):
308
      data_node.append(etree.XML(xml_string, parser=parser))
309 310 311 312
    elif media_type == self.MEDIA_TYPE['TEXT_XML'] and \
         not isinstance(xml_string, str):
      #xml_string could be Partial element if partial XML
      data_node.append(xml_string)
313
    else:
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
      cdata = etree.CDATA(xml_string.decode('utf-8'))
      data_node.text = cdata
    xml = (E.Add(
            E.CmdID('%s' % cmd_id),
            E.Meta(
              E.Type(media_type)
              ),
            E.Item(
              E.Source(
                E.LocURI(gid)
                ),
              data_node
              )
            ))
    if more_data:
      item_node = xml.find('Item')
      item_node.append(Element('MoreData'))
Nicolas Delaby's avatar
Nicolas Delaby committed
331
    return etree.tostring(xml, encoding='utf-8', pretty_print=True)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
332

333
  def deleteXMLObject(self, cmd_id=0, object_gid=None, rid=None, xml_object=''):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
334
    """
Sebastien Robin's avatar
Sebastien Robin committed
335 336
      Delete an object with the SyncML protocol
    """
337 338
    if rid:
      elem_to_append = E.Target(E.LocURI('%s' % rid))
339
    else:
340 341 342 343 344 345 346
      elem_to_append = E.Source(E.LocURI('%s' % object_gid))
    xml = (E.Delete(
             E.CmdID('%s' % cmd_id),
             E.Item(
               elem_to_append
               )
             ))
Nicolas Delaby's avatar
Nicolas Delaby committed
347
    return etree.tostring(xml, encoding='utf-8', pretty_print=True)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
348

349
  def replaceXMLObject(self, cmd_id=0, object=None, xml_string=None,
350
                       more_data=0, gid=None, rid=None, media_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
351
    """
Sebastien Robin's avatar
Sebastien Robin committed
352 353
      Replace an object with the SyncML protocol
    """
354 355
    if rid:
      elem_to_append = E.Target(E.LocURI('%s' % rid))
356
    else:
357 358 359 360 361
      elem_to_append = E.Source(E.LocURI('%s' % gid))
    data_node = Element('Data')
    if not isinstance(xml_string, (str, unicode)):
      data_node.append(xml_string)
    else:
362
      data_node.append(etree.XML(xml_string, parser=parser))
363 364 365 366 367 368 369 370 371 372 373 374 375
    xml = (E.Replace(
             E.CmdID('%s' % cmd_id),
             E.Meta(
               E.Type(media_type)
               ),
             E.Item(
               elem_to_append,
               data_node
               )
             ))
    if more_data:
      item_node = xml.find('Item')
      item_node.append(Element('MoreData'))
Nicolas Delaby's avatar
Nicolas Delaby committed
376
    return etree.tostring(xml, encoding='utf-8', pretty_print=True)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
377

Nicolas Delaby's avatar
Nicolas Delaby committed
378
  def getXupdateObject(self, object_xml=None, old_xml=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
379 380
    """
    Generate the xupdate with the new object and the old xml
Nicolas Delaby's avatar
Nicolas Delaby committed
381 382 383
    """
    erp5diff = ERP5Diff()
    erp5diff.compare(old_xml, object_xml)
384 385 386 387
    if isinstance(erp5diff._result, minidom.Document):
      #XXX While ERP5Diff use minidom, this part needs to be keeped.
      #minidom is buggy, add namespace declaration, and version attributes
      attr_version = erp5diff._result.createAttributeNS(None, 'version')
388 389
      attr_version.value = '1.0'
      erp5diff._result.documentElement.setAttributeNodeNS(attr_version)
390 391
      attr_ns = erp5diff._result.createAttributeNS(None, 'xmlns:xupdate')
      attr_ns.value = 'http://www.xmldb.org/xupdate'
392 393 394 395 396
      erp5diff._result.documentElement.setAttributeNodeNS(attr_ns)
      xupdate = erp5diff._result.toxml('utf-8')
    else:
      #Upper version of ERP5Diff produce valid XML.
      xupdate = erp5diff.outputString()
Nicolas Delaby's avatar
Nicolas Delaby committed
397
    #omit xml declaration
Jean-Paul Smets's avatar
Jean-Paul Smets committed
398 399 400
    xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
    return xupdate

401
  def getSessionIdFromXml(self, xml):
402 403 404
    """
    We will retrieve the session id of the message
    """
405
    return int(xml.xpath('string(/SyncML/SyncHdr/SessionID)'))
406

407
  def getMessageIdFromXml(self, xml):
Sebastien Robin's avatar
Sebastien Robin committed
408 409 410
    """
    We will retrieve the message id of the message
    """
411
    return int(xml.xpath('string(/SyncML/SyncHdr/MsgID)'))
Sebastien Robin's avatar
Sebastien Robin committed
412 413 414 415 416

  def getTarget(self, xml):
    """
    return the target in the SyncHdr section
    """
417
    return '%s' % xml.xpath('string(/SyncML/SyncHdr/Target/LocURI)')
Sebastien Robin's avatar
Sebastien Robin committed
418

Jean-Paul Smets's avatar
Jean-Paul Smets committed
419 420 421 422 423
  def getAlertLastAnchor(self, xml_stream):
    """
      Return the value of the last anchor, in the
      alert section of the xml_stream
    """
424
    return '%s' % xml_stream.xpath('string(.//Alert/Item/Meta/Anchor/Last)')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
425 426 427 428 429 430

  def getAlertNextAnchor(self, xml_stream):
    """
      Return the value of the next anchor, in the
      alert section of the xml_stream
    """
431
    return '%s' % xml_stream.xpath('string(.//Alert/Item/Meta/Anchor/Next)')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
432

433
  def getSourceURI(self, xml):
434 435 436
    """
    return the source uri of the data base
    """
437
    return '%s' % xml.xpath('string(//SyncBody/Alert/Item/Source/LocURI)')
438

439 440 441 442
  def getTargetURI(self, xml):
    """
    return the target uri of the data base
    """
443
    return '%s' % xml.xpath('string(//SyncBody/Alert/Item/Target/LocURI)')
444

445
  def getSubscriptionUrlFromXML(self, xml):
446 447 448
    """
    return the source URI of the syncml header
    """
449
    return '%s' % xml.xpath('string(//SyncHdr/Source/LocURI)')
450

Jean-Paul Smets's avatar
Jean-Paul Smets committed
451 452 453 454
  def getStatusTarget(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
455
    return '%s' % xml.xpath('string(TargetRef)')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
456 457 458 459 460

  def getStatusCode(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
461 462
    status_code = '%s' % xml.xpath('string(Data)')
    if status_code:
463
      return int(status_code)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
464 465
    return None

466 467 468 469
  def getStatusCommand(self, xml):
    """
      Return the value of the command inside the xml_stream
    """
470
    cmd = None
471
    if xml.tag == 'Status':
Nicolas Delaby's avatar
Nicolas Delaby committed
472
      cmd = '%s' % xml.xpath('string(Cmd)')
473
    return cmd
474

475 476 477
  def getCred(self, xml):
    """
      return the credential information : type, format and data
Nicolas Delaby's avatar
Nicolas Delaby committed
478
    """
479 480 481
    format = '%s' % xml.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Format'])")
    type = '%s' % xml.xpath("string(/SyncML/SyncHdr/Cred/Meta/*[local-name() = 'Type'])")
    data = '%s' % xml.xpath('string(/SyncML/SyncHdr/Cred/Data)')
482 483 484

    return (format, type, data)

485
  def checkCred(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
486
    """
487
      Check if there's a Cred section in the xml_stream
Jean-Paul Smets's avatar
Jean-Paul Smets committed
488
    """
489
    return bool(xml_stream.xpath('string(/SyncML/SyncHdr/Cred)'))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
490

491
  def getChal(self, xml):
Sebastien Robin's avatar
Sebastien Robin committed
492
    """
493
      return the chalenge information : format and type
494
    """
495 496
    format = '%s' % xml.xpath("string(//*[local-name() = 'Format'])")
    type = '%s' % xml.xpath("string(//*[local-name() = 'Type'])")
497 498 499
    return (format, type)

  def checkChal(self, xml_stream):
Sebastien Robin's avatar
Sebastien Robin committed
500
    """
501 502
      Check if there's a Chal section in the xml_stream
    """
503
    return bool(xml_stream.xpath('string(/SyncML/SyncBody/Status/Chal)'))
504

505 506 507 508
  def checkMap(self, xml_stream):
    """
      Check if there's a Map section in the xml_stream
    """
509
    return bool(xml_stream.xpath('string(/SyncML/SyncBody/Map)'))
510 511 512 513 514 515

  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)
    """
516
    item_list = xml_stream.xpath('/SyncML/SyncBody/Map/MapItem')
517
    for map_item in item_list:
518
      gid = '%s' % map_item.xpath('string(.//Target/LocURI)')
519
      signature = subscriber.getSignatureFromGid(gid)
520
      rid = '%s' % map_item.xpath('string(.//Source/LocURI)')
521
      signature.setRid(rid)
522

523
  def getAlertCodeFromXML(self, xml_stream):
524 525 526
    """
      Return the value of the alert code inside the full syncml message
    """
527 528
    alert_code = '%s' % xml_stream.xpath('string(/SyncML/SyncBody/Alert/Data)')
    if alert_code:
529 530 531
      return int(alert_code)
    else:
      return None
Sebastien Robin's avatar
Sebastien Robin committed
532

Jean-Paul Smets's avatar
Jean-Paul Smets committed
533 534
  def checkAlert(self, xml_stream):
    """
Sebastien Robin's avatar
Sebastien Robin committed
535
      Check if there's an Alert section in the xml_stream
Jean-Paul Smets's avatar
Jean-Paul Smets committed
536
    """
537
    return bool(xml_stream.xpath('string(/SyncML/SyncBody/Alert)'))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
538 539 540 541 542

  def checkSync(self, xml_stream):
    """
      Check if there's an Sync section in the xml_xtream
    """
543
    return bool(xml_stream.xpath('string(/SyncML/SyncBody/Sync)'))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
544

545
  def checkStatus(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
546 547 548
    """
      Check if there's a Status section in the xml_xtream
    """
549
    return bool(xml_stream.xpath('string(/SyncML/SyncBody/Status)'))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
550

551
  def getSyncActionList(self, xml_stream):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
552
    """
553
    return the list of the action (could be "add", "replace", "delete").
Jean-Paul Smets's avatar
Jean-Paul Smets committed
554
    """
555 556 557 558 559 560 561 562
    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 = []
563 564
    status_node_list = xml_stream.xpath('//Status')
    for status in status_node_list:
565
      tmp_dict = {}
566 567 568 569
      tmp_dict['cmd'] = '%s' % status.xpath('string(./Cmd)')
      tmp_dict['code'] = '%s' % status.xpath('string(./Data)')
      tmp_dict['source'] = '%s' % status.xpath('string(./SourceRef)')
      tmp_dict['target'] = '%s' % status.xpath('string(./TargetRef)')
570 571
      status_list.append(tmp_dict)
    return status_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
572

573 574 575 576
  def getDataText(self, action):
    """
    return the section data in text form, it's usefull for the VCardConduit
    """
577
    return '%s' % action.xpath('string(.//Item/Data)')
578

Jean-Paul Smets's avatar
Jean-Paul Smets committed
579 580 581 582
  def getDataSubNode(self, action):
    """
      Return the node starting with <object....> of the action
    """
583 584 585
    object_node_list = action.xpath('.//Item/Data/*[1]')
    if object_node_list:
      return object_node_list[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
586 587
    return None

588
  def getActionId(self, action):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
589 590 591
    """
      Return the rid of the object described by the action
    """
592 593 594
    id = '%s' % action.xpath('string(.//Item/Source/LocURI)')
    if not id:
      id = '%s' % action.xpath('string(.//Item/Target/LocURI)')
595
    return id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
596 597 598 599 600

  def checkActionMoreData(self, action):
    """
      Return the rid of the object described by the action
    """
601
    return bool(action.xpath('.//Item/MoreData'))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
602 603 604 605 606

  def getActionType(self, action):
    """
      Return the type of the object described by the action
    """
607
    return '%s' % action.xpath('string(.//Meta/Type)')
608

609
  def cutXML(self, xml_string):
610
    """
611
    Sliced a xml tree a return two fragment
612
    """
613 614 615 616 617 618
    line_list = xml_string.split('\n')
    short_string = '\n'.join(line_list[:self.MAX_LINES])
    rest_string = '\n'.join(line_list[self.MAX_LINES:])
    xml_string = etree.Element('Partial')
    xml_string.text = etree.CDATA(short_string.decode('utf-8'))
    return xml_string, rest_string
619

620
  def getSyncMLData(self, domain=None, remote_xml=None, cmd_id=0,
Nicolas Delaby's avatar
Nicolas Delaby committed
621
                    subscriber=None, xml_confirmation_list=None, conduit=None,
Nicolas Delaby's avatar
Nicolas Delaby committed
622
                    max=None, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
623
    """
624 625
    This generate the syncml data message. This returns a string
    with all modification made locally (ie replace, add ,delete...)
626 627 628

    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
629
    """
630
    #LOG('getSyncMLData starting...', DEBUG, domain.getId())
Sebastien Robin's avatar
Sebastien Robin committed
631 632
    if isinstance(conduit, str):
      conduit = self.getConduitByName(conduit)
Nicolas Delaby's avatar
Nicolas Delaby committed
633 634
    if xml_confirmation_list is None:
      xml_confirmation_list = []
635
    local_gid_list = []
Nicolas Delaby's avatar
Nicolas Delaby committed
636
    syncml_data_list = kw.get('syncml_data_list', [])
Nicolas Delaby's avatar
Nicolas Delaby committed
637
    result = {'finished':1}
638
    if isinstance(remote_xml, (str, unicode)):
639
      remote_xml = etree.XML(remote_xml, parser=parser)
640 641 642 643
    if domain.isOneWayFromServer():
      #Do not set object_path_list, subscriber send nothing
      subscriber.setRemainingObjectPathList([])
    elif subscriber.getRemainingObjectPathList() is None:
644
      object_list = domain.getObjectList()
645
      object_path_list = [x.getPhysicalPath() for x in object_list]
646
      subscriber.setRemainingObjectPathList(object_path_list)
647 648 649 650 651 652
      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:
653
          #LOG('getSyncMLData :', DEBUG, 'object:%s,  objectTitle:%s, local_gid_list:%s' % (object, object.getTitle(), local_gid_list))
654 655 656
          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)])
657 658
            if number:
              gid = '%s__%s' %  (gid, str(number+1))
659 660
          gid_not_encoded_list.append(gid)
          local_gid_list.append(b16encode(gid))
661
          #LOG('getSyncMLData :', DEBUG,'gid_not_encoded_list:%s, local_gid_list:%s, gid:%s' % (gid_not_encoded_list, local_gid_list, gid))
662
      else:
663
        local_gid_list = [domain.getGidFromObject(x) for x in object_list]
664 665

      # Objects to remove
666
      #LOG('getSyncMLData remove object to remove ...', DEBUG, '')
667
      object_gid_deleted = []
668
      for object_gid in subscriber.getGidList():
Nicolas Delaby's avatar
Nicolas Delaby committed
669
        if object_gid not in local_gid_list:
670
          # This is an object to remove
671
          signature = subscriber.getSignatureFromGid(object_gid)
672 673
          if signature.getStatus() != self.PARTIAL:
            # If partial, then we have a signature but no local object
674
            xml_object = signature.getXML()
675
            if xml_object is not None: # This prevent to delete an object that
676
                                       # we were not able to create
677
              rid = signature.getRid()
678
              object_gid_deleted.append(object_gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
679
              syncml_data_list.append(self.deleteXMLObject(
680 681 682
                                      xml_object=signature.getXML() or '',
                                      object_gid=object_gid,
                                      rid=rid,
Nicolas Delaby's avatar
Nicolas Delaby committed
683
                                      cmd_id=cmd_id))
684
              cmd_id += 1
Nicolas Delaby's avatar
Nicolas Delaby committed
685 686 687 688
      #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)
689
    local_gid_list = []
690
    loop = 0
691
    for object_path in subscriber.getRemainingObjectPathList():
Nicolas Delaby's avatar
Nicolas Delaby committed
692 693 694
      if max is not None and loop >= max:
        result['finished'] = 0
        break
695
      #LOG('getSyncMLData object_path', INFO, object_path)
696
      object = self.unrestrictedTraverse(object_path)
697
      status = self.SENT
698
      object_gid = domain.getGidFromObject(object)
699
      if not object_gid:
Nicolas Delaby's avatar
Nicolas Delaby committed
700
        continue
701
      local_gid_list += [object_gid]
702
      force = 0
Nicolas Delaby's avatar
Nicolas Delaby committed
703
      if ''.join(syncml_data_list).count('\n') < self.MAX_LINES and not \
704 705
          object.id.startswith('.'):
        # If not we have to cut
706 707 708 709 710 711 712
        #LOG('getSyncMLData', 0, 'object_path: %s' % '/'.join(object_path))
        #LOG('getSyncMLData', 0, 'xml_mapping: %s' % str(domain.getXMLMapping()))
        #LOG('getSyncMLData', 0, 'code: %s' % str(self.getAlertCodeFromXML(remote_xml)))
        #LOG('getSyncMLData', 0, 'gid_list: %s' % str(local_gid_list))
        #LOG('getSyncMLData', 0, 'subscriber.getGidList: %s' % subscriber.getGidList())
        #LOG('getSyncMLData', 0, 'hasSignature: %s' % str(subscriber.hasSignature(object_gid)))
        #LOG('getSyncMLData', 0, 'alert_code == slowsync: %s' % str(self.getAlertCodeFromXML(remote_xml) == self.SLOW_SYNC))
713

Nicolas Delaby's avatar
Nicolas Delaby committed
714 715 716
        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:
717
          #LOG('getSyncMLData', DEBUG, 'signature.getStatus: %s' % signature.getStatus())
718
        status = self.SENT
719
        more_data = 0
720
        # For the case it was never synchronized, we have to send everything
Nicolas Delaby's avatar
Nicolas Delaby committed
721
        if signature is not None and signature.getXMLMapping() is None:
722
          pass
Nicolas Delaby's avatar
Nicolas Delaby committed
723
        elif signature is None or (signature.getXML() is None and \
724
            signature.getStatus() != self.PARTIAL) or \
725
            self.getAlertCodeFromXML(remote_xml) == self.SLOW_SYNC:
726
          #LOG('getSyncMLData', DEBUG, 'Current object.getPath: %s' % object.getPath())
727
          xml_string = domain.getXMLFromObject(object)
728
          gid = subscriber.getGidFromObject(object)
729
          signature = Signature(id=gid, object=object).__of__(subscriber)
730
          signature.setTempXML(xml_string)
731
          if xml_string.count('\n') > self.MAX_LINES:
732 733
            xml_string, rest_string = self.cutXML(xml_string)
            more_data = 1
734
            signature.setPartialXML(rest_string)
735
            status = self.PARTIAL
736
            signature.setAction('Add')
737 738
          #in fisrt, we try with rid if there is one
          gid = signature.getRid() or signature.getGid()
Nicolas Delaby's avatar
Nicolas Delaby committed
739
          syncml_data_list.append(self.addXMLObject(
740 741 742 743 744
                                  cmd_id=cmd_id,
                                  object=object,
                                  gid=gid,
                                  xml_string=xml_string,
                                  more_data=more_data,
Nicolas Delaby's avatar
Nicolas Delaby committed
745
                                  media_type=subscriber.getMediaType()))
746 747 748
          cmd_id += 1
          signature.setStatus(status)
          subscriber.addSignature(signature)
749 750 751
        elif signature.getStatus() == self.NOT_SYNCHRONIZED \
            or signature.getStatus() == self.PUB_CONFLICT_MERGE:
          # We don't have synchronized this object yet
752
          xml_object = domain.getXMLFromObject(object)
753 754
          #LOG('getSyncMLData', DEBUG, 'checkMD5: %s' % str(signature.checkMD5(xml_object)))
          #LOG('getSyncMLData', DEBUG, 'getStatus: %s' % str(signature.getStatus()))
755
          if signature.getStatus() == self.PUB_CONFLICT_MERGE:
Nicolas Delaby's avatar
Nicolas Delaby committed
756 757 758 759 760
            xml_confirmation_list.append(self.SyncMLConfirmation(
                                    cmd_id=cmd_id,
                                    source_ref=signature.getGid(),
                                    sync_code=self.CONFLICT_MERGE,
                                    cmd='Replace'))
761
          set_synchronized = 1
762
          if not signature.checkMD5(xml_object):
763
            set_synchronized = 0
764
            # This object has changed on this side, we have to generate some xmldiff
765
            if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']:
766
              xml_string = self.getXupdateObject(xml_object, signature.getXML())
767
            else: #if there is no xml, we re-send all the object
768
              xml_string = xml_object
769 770 771
            if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
              xml_string = xml_object
            elif xml_string.count('\n') > self.MAX_LINES:
772
              # This make comment fails, so we need to replace
773
              xml_string, rest_string = self.cutXML(xml_string)
774
              more_data = 1
775 776 777
              signature.setPartialXML(rest_string)
              status = self.PARTIAL
              signature.setAction('Replace')
778
              signature.setStatus(status)
779 780
            rid = signature.getRid()#in fisrt, we try with rid if there is one
            gid = signature.getGid()
Nicolas Delaby's avatar
Nicolas Delaby committed
781
            syncml_data_list.append(self.replaceXMLObject(
782 783 784 785
                                        cmd_id=cmd_id, object=object,
                                        gid=gid, rid=rid,
                                        xml_string=xml_string,
                                        more_data=more_data,
Nicolas Delaby's avatar
Nicolas Delaby committed
786
                                        media_type=subscriber.getMediaType()))
787 788
            cmd_id += 1
            signature.setTempXML(xml_object)
789 790
          # Now we can apply the xupdate from the subscriber
          subscriber_xupdate = signature.getSubscriberXupdate()
791
          #LOG('getSyncMLData subscriber_xupdate', DEBUG, subscriber_xupdate)
792 793
          if subscriber_xupdate is not None:
            old_xml = signature.getXML()
794 795 796 797 798 799
            conduit.updateNode(
                        xml=subscriber_xupdate,
                        object=object,
                        previous_xml=old_xml,
                        force=(domain.getDomainType() == self.SUB),
                        simulate=0)
800
            xml_object = domain.getXMLFromObject(object)
801 802 803
            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
804
            signature.setStatus(self.SYNCHRONIZED)
805
        elif signature.getStatus() == self.PUB_CONFLICT_CLIENT_WIN:
806
          # We have decided to apply the update
807
          # XXX previous_xml will be geXML instead of getTempXML because
808 809
          # some modification was already made and the update
          # may not apply correctly
810
          xml_update = signature.getPartialXML()
811 812 813 814 815
          conduit.updateNode(
                      xml=signature.getPartialXML(),
                      object=object,
                      previous_xml=signature.getXML(),
                      force=1)
Nicolas Delaby's avatar
Nicolas Delaby committed
816 817 818 819 820
          xml_confirmation_list.append(self.SyncMLConfirmation(
                                  cmd_id=cmd_id,
                                  target_ref=object_gid,
                                  sync_code=self.CONFLICT_CLIENT_WIN,
                                  cmd='Replace'))
821
          signature.setStatus(self.SYNCHRONIZED)
822
        elif signature.getStatus() == self.PARTIAL:
823
          xml_string = signature.getPartialXML()
824 825 826 827 828 829 830
          xml_to_send = Element('Partial')
          xml_to_send.text = etree.CDATA(xml_string.decode('utf-8'))
          if(subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']):
            xml_to_send = domain.getXMLFromObject(object)
          elif xml_string.count('\n') > self.MAX_LINES:
            xml_to_send, rest_string = self.cutXML(xml_string)
            more_data = 1
831 832 833
            signature.setPartialXML(rest_string)
            status = self.PARTIAL
          signature.setStatus(status)
834
          if signature.getAction() == 'Replace':
835 836
            rid = signature.getRid()#in fisrt, we try with rid if there is one
            gid = signature.getGid()
Nicolas Delaby's avatar
Nicolas Delaby committed
837
            syncml_data_list.append(self.replaceXMLObject(
838 839 840 841
                                       cmd_id=cmd_id,
                                       object=object,
                                       gid=gid,
                                       rid=rid,
842
                                       xml_string=xml_to_send,
843
                                       more_data=more_data,
Nicolas Delaby's avatar
Nicolas Delaby committed
844
                                       media_type=subscriber.getMediaType()))
845
          elif signature.getAction() == 'Add':
846 847
            #in fisrt, we try with rid if there is one
            gid = signature.getRid() or signature.getGid()
Nicolas Delaby's avatar
Nicolas Delaby committed
848 849 850 851 852 853 854
            syncml_data_list.append(self.addXMLObject(
                                        cmd_id=cmd_id,
                                        object=object,
                                        gid=gid,
                                        xml_string=xml_to_send,
                                        more_data=more_data,
                                        media_type=subscriber.getMediaType()))
Sebastien Robin's avatar
Sebastien Robin committed
855 856
        if not more_data:
          subscriber.removeRemainingObjectPath(object_path)
857
      else:
Nicolas Delaby's avatar
Nicolas Delaby committed
858
        result['finished'] = 1
859
        break
860
      loop += 1
Nicolas Delaby's avatar
Nicolas Delaby committed
861 862
    result['syncml_data_list'] = syncml_data_list
    result['xml_confirmation_list'] = xml_confirmation_list
863 864
    result['cmd_id'] = cmd_id
    return result
865

866
  def applyActionList(self, domain=None, subscriber=None, cmd_id=0,
867
                      remote_xml=None, conduit=None, simulate=0):
868 869 870
    """
    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
871
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
872
    xml_confirmation_list = []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
873
    has_next_action = 0
874
    gid_from_xml_list = []
875
    destination = self.unrestrictedTraverse(domain.getDestinationPath())
876 877
    #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))
878
    for action in self.getSyncActionList(remote_xml):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
879 880 881
      conflict_list = []
      status_code = self.SUCCESS
      # Thirst we have to check the kind of action it is
882
      partial_data = '%s' % action.xpath('string(.//Item/Data/Partial)')
883
      rid = self.getActionId(action)
884 885 886
      if action.tag != 'Delete':
        if getattr(conduit, 'getGidFromXML', None) is not None and \
           conduit.getGidFromXML(self.getDataText(action), gid_from_xml_list):
887
          gid = conduit.getGidFromXML(self.getDataText(action),
888
                                      gid_from_xml_list)
889 890
          gid_from_xml_list.append(gid)
          gid = b16encode(gid)
891
        else:
892
          gid = rid
893
      else:
894
        gid = rid
895
      object_id = domain.generateNewIdWithGenerator(object=destination, gid=gid)
896
      signature = subscriber.getSignatureFromGid(gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
897
      if signature is not None and rid != gid:
898 899 900
        #in this case, the object was created on another subscriber than erp5
        # and we should save it's remote id
        signature.setRid(rid)
901
      #LOG('gid == rid ?', DEBUG, 'gid=%s, rid=%s' % (gid, rid))
902
      object = subscriber.getObjectFromGid(gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
903
      if object is None and not domain.getSynchronizeWithERP5Sites():
904 905 906 907 908
        #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, '')
909
        object = subscriber.getObjectFromRid(rid)
910
        signature = subscriber.getSignatureFromRid(rid)
911
        if signature is not None:
912
          gid = signature.getId()
913
      #LOG('applyActionList subscriber.getObjectFromGid %s' % gid, DEBUG, object)
Nicolas Delaby's avatar
Nicolas Delaby committed
914
      if signature is None:
915
        #LOG('applyActionList, signature is None', DEBUG, signature)
916
        if gid == rid:
Nicolas Delaby's avatar
Nicolas Delaby committed
917
          signature = Signature(id=gid, status=self.NOT_SYNCHRONIZED,
918
                                object=object).__of__(subscriber)
919 920
        else:
          signature = Signature(rid=rid, id=gid, status=self.NOT_SYNCHRONIZED,
921
                                object=object).__of__(subscriber)
922
        signature.setObjectId(object_id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
923 924
        subscriber.addSignature(signature)
      force = signature.getForce()
925
      if not self.checkActionMoreData(action):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
926
        data_subnode = None
927
        if partial_data:
928
          signature_partial_xml = signature.getPartialXML()
929 930
          if signature_partial_xml:
            data_subnode = signature_partial_xml + partial_data
931 932
          else:
            data_subnode = partial_data
933
          #LOG('applyActionList', DEBUG, 'data_subnode: %s' % data_subnode)
934
          if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']:
935
            data_subnode = etree.XML(data_subnode, parser=parser)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
936
        else:
937
          if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
938
            data_subnode = self.getDataText(action)
939
          else:
940
            data_subnode = self.getDataSubNode(action)
941
        if action.tag == 'Add':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
942
          # Then store the xml of this new subobject
943
          reset = 0
944
          if object is None:
945 946 947
            add_data = conduit.addNode(xml=data_subnode,
                                       object=destination,
                                       object_id=object_id)
948
            conflict_list.extend(add_data['conflict_list'])
949 950
            # Retrieve directly the object from addNode
            object = add_data['object']
Nicolas Delaby's avatar
Nicolas Delaby committed
951 952
            if object is not None:
              signature.setPath(object.getPhysicalPath())
953
              signature.setObjectId(object.getId())
954
          else:
955
            reset = 1
956 957
            #Object was retrieve but need to be updated without recreated
            #usefull when an object is only deleted by workflow.
958
            if data_subnode is not None:
959 960 961
              if not isinstance(data_subnode, str):
                xml_string = etree.tostring(data_subnode, encoding='utf-8')
              actual_xml = subscriber.getXMLFromObject(object=object, force=1)
962
              data_subnode = self.getXupdateObject(xml_string, actual_xml)
Nicolas Delaby's avatar
Nicolas Delaby committed
963
            conflict_list.extend(conduit.updateNode(
964 965 966 967
                                        xml=data_subnode,
                                        object=object,
                                        previous_xml=signature.getXML(),
                                        force=force,
Nicolas Delaby's avatar
Nicolas Delaby committed
968
                                        simulate=simulate))
969 970
            xml_object = domain.getXMLFromObject(object)
            signature.setTempXML(xml_object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
971
          if object is not None:
972
            #LOG('applyActionList', DEBUG, 'addNode, found the object')
973 974 975
            if reset:
              #After a reset we want copy the LAST XML view on Signature.
              #this implementation is not sufficient, need to be improved.
976
              if not isinstance(data_subnode, str):
Nicolas Delaby's avatar
Nicolas Delaby committed
977 978
                xml_object = etree.tostring(data_subnode, encoding='utf-8',
                                            pretty_print=True)
979 980
              else:
                xml_object = data_subnode
981 982
            else:
              xml_object = domain.getXMLFromObject(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
983
            signature.setStatus(self.SYNCHRONIZED)
984
            #signature.setId(object.getId())
985
            signature.setPath(object.getPhysicalPath())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
986
            signature.setXML(xml_object)
Nicolas Delaby's avatar
Nicolas Delaby committed
987 988 989 990 991
            xml_confirmation_list.append(self.SyncMLConfirmation(
                                    cmd_id=cmd_id,
                                    cmd='Add',
                                    sync_code=self.ITEM_ADDED,
                                    remote_xml=action))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
992
            cmd_id +=1
993
        elif action.tag == 'Replace':
994
          #LOG('applyActionList', DEBUG, 'object: %s will be updated...' % str(object))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
995
          if object is not None:
996
            #LOG('applyActionList', DEBUG, 'object: %s will be updated...' % object.id)
997
            signature = subscriber.getSignatureFromGid(gid)
998 999
            if signature is None:
              signature = subscriber.getSignatureFromRid(gid)
1000
            #LOG('applyActionList', DEBUG, 'previous signature: %s' % str(signature))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1001
            previous_xml = signature.getXML()
1002 1003 1004 1005 1006 1007
            conflict_list += conduit.updateNode(
                                        xml=data_subnode,
                                        object=object,
                                        previous_xml=signature.getXML(),
                                        force=force,
                                        simulate=simulate)
1008
            xml_object = domain.getXMLFromObject(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1009
            signature.setTempXML(xml_object)
1010
            if conflict_list:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1011 1012
              status_code = self.CONFLICT
              signature.setStatus(self.CONFLICT)
1013 1014
              signature.setConflictList(signature.getConflictList()+conflict_list)
              data_subnode_string = etree.tostring(data_subnode, encoding='utf-8')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1015
              signature.setPartialXML(data_subnode_string)
1016
            elif not simulate:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1017
              signature.setStatus(self.SYNCHRONIZED)
Nicolas Delaby's avatar
Nicolas Delaby committed
1018 1019 1020 1021 1022
            xml_confirmation_list.append(self.SyncMLConfirmation(
                                    cmd_id=cmd_id,
                                    cmd='Replace',
                                    sync_code=status_code,
                                    remote_xml=action))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1023
            cmd_id +=1
1024
            if simulate:
Sebastien Robin's avatar
Sebastien Robin committed
1025
              # This means we are on the publisher side and we want to store
1026 1027
              # the xupdate from the subscriber and we also want to generate
              # the current xupdate from the last synchronization
1028
              data_subnode_string = etree.tostring(data_subnode, encoding='utf-8')
1029
              #LOG('applyActionList, subscriber_xupdate:', TRACE, data_subnode_string)
1030 1031
              signature.setSubscriberXupdate(data_subnode_string)

1032
        elif action.tag == 'Delete':
1033
          object_id = signature.getId()
1034
          #LOG('applyActionList Delete on : ', DEBUG, (signature.getId(), subscriber.getObjectFromGid(object_id)))
1035
          if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
1036
            data_subnode = self.getDataText(action)
1037
          else:
1038
            data_subnode = self.getDataSubNode(action)
1039
          #LOG('applyActionList, object gid to delete :', 0, subscriber.getObjectFromGid(object_id))
1040
          if subscriber.getObjectFromGid(object_id) is not None:
1041
          #if the object exist:
1042 1043 1044 1045
            conduit.deleteNode(
                        xml=data_subnode,
                        object=destination,
                        object_id=subscriber.getObjectFromGid(object_id).getId())
1046
            subscriber.delSignature(gid)
Nicolas Delaby's avatar
Nicolas Delaby committed
1047 1048 1049 1050 1051
          xml_confirmation_list.append(self.SyncMLConfirmation(
                                  cmd_id=cmd_id,
                                  cmd='Delete',
                                  sync_code=status_code,
                                  remote_xml=action))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1052 1053 1054
      else: # We want to retrieve more data
        signature.setStatus(self.PARTIAL)
        previous_partial = signature.getPartialXML() or ''
1055
        previous_partial += partial_data
1056
        #LOG('applyActionList', DEBUG, 'setPartialXML: %s' % str(previous_partial))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1057
        signature.setPartialXML(previous_partial)
1058 1059
        #LOG('applyActionList', DEBUG, 'previous_partial: %s' % str(previous_partial))
        #LOG('applyActionList', DEBUG, 'waiting more data for :%s' % signature.getId())
Nicolas Delaby's avatar
Nicolas Delaby committed
1060 1061 1062 1063 1064
        xml_confirmation_list.append(self.SyncMLConfirmation(
                                cmd_id=cmd_id,
                                cmd=action.tag,
                                sync_code=self.WAITING_DATA,
                                remote_xml=action))
1065
      if conflict_list and signature is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1066 1067
        # We had a conflict
        signature.setStatus(self.CONFLICT)
1068

Nicolas Delaby's avatar
Nicolas Delaby committed
1069
    return (xml_confirmation_list, has_next_action, cmd_id)
1070

1071
  def applyStatusList(self, subscriber=None, remote_xml=None):
1072 1073 1074 1075
    """
    This read a list of status list (ie syncml confirmations).
    This method have to change status codes on signatures
    """
1076
    status_list = self.getSyncBodyStatusList(remote_xml)
1077 1078
    has_status_list = 0
    destination_waiting_more_data = 0
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 1105 1106 1107 1108 1109
    for status in status_list:
      if not status['code']:
        continue
      status_cmd = status['cmd']
      object_gid = status['source']
      if not object_gid:
        object_gid = status['target']
      status_code = int(status['code'])
      if status_cmd in ('Add', 'Replace',):
        has_status_list = 1
        signature = subscriber.getSignatureFromGid(object_gid)
        if signature is 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':
        has_status_list = 1
        if status_code == self.SUCCESS:
1110
          signature = subscriber.getSignatureFromGid(object_gid)
1111 1112
          if signature is None and \
          not(subscriber.getSynchronizeWithERP5Sites()):
1113
            signature = subscriber.getSignatureFromRid(object_gid)
1114 1115
          if signature is not None:
            subscriber.delSignature(signature.getGid())
1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133
    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

1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152
  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

1153 1154 1155 1156
  def SyncModif(self, domain, remote_xml):
    """
    Modification Message, this is used after the first
    message in order to send modifications.
1157 1158
    Send the server modification, this happens after the Synchronization
    initialization
1159
    """
1160
    has_response = 0 #check if syncmodif replies to this messages
1161
    cmd_id = 1 # specifies a SyncML message-unique command identifier
1162
    #LOG('SyncModif', DEBUG, 'Starting... domain: %s' % domain.getId())
1163
    # Get informations from the header
1164 1165
    xml_header = remote_xml[0]
    if xml_header.tag != "SyncHdr":
1166
      LOG('SyncModif', INFO, 'This is not a SyncML Header')
Sebastien Robin's avatar
Sebastien Robin committed
1167
      raise ValueError, "Sorry, This is not a SyncML Header"
1168 1169

    subscriber = domain # If we are the client, this is fine
1170
    simulate = 0 # used by applyActionList, should be 0 for client
1171
    if domain.domain_type == self.PUB:
1172
      simulate = 1
1173
      subscription_url = self.getSubscriptionUrlFromXML(xml_header)
1174
      subscriber = domain.getSubscriber(subscription_url)
1175

1176 1177
    # We have to check if this message was not already, this can be dangerous
    # to update two times the same object
1178
    message_id = self.getMessageIdFromXml(remote_xml)
Sebastien Robin's avatar
Sebastien Robin committed
1179 1180
    correct_message = subscriber.checkCorrectRemoteMessageId(message_id)
    if not correct_message: # We need to send again the message
1181
      LOG('SyncModif, no correct message:', INFO, "sending again...")
1182
      last_xml = subscriber.getLastSentMessage()
1183
      LOG("SyncModif last_xml :", INFO, last_xml)
1184 1185 1186
      remote_xml = etree.tostring(remote_xml, encoding='utf-8',
                                  xml_declaration=True,
                                  pretty_print=True)
Nicolas Delaby's avatar
Nicolas Delaby committed
1187
      LOG("SyncModif remote_xml :", INFO, remote_xml)
1188
      if last_xml:
1189 1190
        has_response = 1
        if domain.domain_type == self.PUB: # We always reply
1191 1192 1193 1194
          self.sendResponse(
                    from_url=domain.publication_url,
                    to_url=subscriber.subscription_url,
                    sync_id=domain.getTitle(),
1195
                    xml=last_xml, domain=domain,
1196
                    content_type=domain.getSyncContentType())
1197
        elif domain.domain_type == self.SUB:
1198 1199 1200 1201 1202 1203 1204 1205
          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}
1206 1207
    subscriber.setLastSentMessage('')

1208
    # First apply the list of status codes
Nicolas Delaby's avatar
Nicolas Delaby committed
1209
    (destination_waiting_more_data, has_status_list) = self.applyStatusList(
1210 1211
                                                      subscriber=subscriber,
                                                      remote_xml=remote_xml)
1212

1213
    alert_code = self.getAlertCodeFromXML(remote_xml)
Kevin Deldycke's avatar
Kevin Deldycke committed
1214
    # Import the conduit and get it
1215
    conduit = self.getConduitByName(subscriber.getConduit())
1216
    # Then apply the list of actions
Nicolas Delaby's avatar
Nicolas Delaby committed
1217
    (xml_confirmation_list, has_next_action, cmd_id) = self.applyActionList(
1218 1219 1220 1221 1222
                                          cmd_id=cmd_id,
                                          domain=domain,
                                          subscriber=subscriber,
                                          remote_xml=remote_xml,
                                          conduit=conduit, simulate=simulate)
Nicolas Delaby's avatar
Nicolas Delaby committed
1223
    xml = Element('SyncML')
1224

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1225 1226
    # syncml header
    if domain.domain_type == self.PUB:
Nicolas Delaby's avatar
Nicolas Delaby committed
1227
      xml.append(self.SyncMLHeader(
1228 1229 1230 1231
                  subscriber.getSessionId(),
                  subscriber.incrementMessageId(),
                  subscriber.getSubscriptionUrl(),
                  domain.getPublicationUrl()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1232
    elif domain.domain_type == self.SUB:
Nicolas Delaby's avatar
Nicolas Delaby committed
1233
      xml.append(self.SyncMLHeader(
1234 1235 1236
                  domain.getSessionId(), domain.incrementMessageId(),
                  domain.getPublicationUrl(),
                  domain.getSubscriptionUrl()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1237 1238

    # syncml body
Nicolas Delaby's avatar
Nicolas Delaby committed
1239
    sync_body = SubElement(xml, 'SyncBody')
1240

1241 1242 1243 1244 1245
    xml_status, cmd_id = self.SyncMLStatus(
                                    remote_xml,
                                    self.SUCCESS,
                                    cmd_id,
                                    subscriber.getNextAnchor(),
Nicolas Delaby's avatar
Nicolas Delaby committed
1246 1247
                                    subscription=subscriber)
    sync_body.extend(xml_status)
1248

Jean-Paul Smets's avatar
Jean-Paul Smets committed
1249 1250
    destination_url = ''
    # alert message if we want more data
1251
    if destination_waiting_more_data:
Nicolas Delaby's avatar
Nicolas Delaby committed
1252 1253 1254 1255 1256 1257 1258
      sync_body.append(self.SyncMLAlert(
                        cmd_id,
                        self.WAITING_DATA,
                        subscriber.getTargetURI(),
                        subscriber.getSourceURI(),
                        subscriber.getLastAnchor(),
                        subscriber.getNextAnchor()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1259
    # Now we should send confirmations
1260
    cmd_id_before_getsyncmldata = cmd_id
1261
    cmd_id = cmd_id+1
1262
    if domain.getActivityEnabled():
1263
      #use activities to get SyncML data.
Nicolas Delaby's avatar
Nicolas Delaby committed
1264
      remote_xml = etree.tostring(remote_xml, encoding='utf-8',
1265
                                    xml_declaration=True, pretty_print=False)
Nicolas Delaby's avatar
Nicolas Delaby committed
1266 1267
      xml_tree = etree.tostring(xml, encoding='utf-8', xml_declaration=True,
                                pretty_print=False)
1268
      domain.activate(activity='SQLQueue',
Nicolas Delaby's avatar
Nicolas Delaby committed
1269 1270
                      tag=domain.getId(),
                      priority=self.PRIORITY).activateSyncModif(
1271
                      domain_relative_url=domain.getRelativeUrl(),
Nicolas Delaby's avatar
Nicolas Delaby committed
1272
                      remote_xml=remote_xml,
Nicolas Delaby's avatar
Nicolas Delaby committed
1273
                      xml_tree=xml_tree,
Nicolas Delaby's avatar
Nicolas Delaby committed
1274 1275
                      subscriber_relative_url=subscriber.getRelativeUrl(),
                      cmd_id=cmd_id,
Nicolas Delaby's avatar
Nicolas Delaby committed
1276 1277
                      xml_confirmation_list=xml_confirmation_list,
                      syncml_data_list=[],
Nicolas Delaby's avatar
Nicolas Delaby committed
1278 1279 1280
                      cmd_id_before_getsyncmldata=cmd_id_before_getsyncmldata,
                      has_status_list=has_status_list,
                      has_response=has_response )
Nicolas Delaby's avatar
Nicolas Delaby committed
1281
      return {'has_response':1, 'xml':''}
1282 1283
    else:
      result = self.getSyncMLData(domain=domain,
1284 1285
                             remote_xml=remote_xml,
                             subscriber=subscriber,
1286
                             cmd_id=cmd_id,
Nicolas Delaby's avatar
Nicolas Delaby committed
1287
                             xml_confirmation_list=xml_confirmation_list,
1288
                             conduit=conduit,
1289
                             max=None)
Nicolas Delaby's avatar
Nicolas Delaby committed
1290 1291
      syncml_data_list = result['syncml_data_list']
      xml_confirmation_list = result['xml_confirmation_list']
1292
      cmd_id = result['cmd_id']
Nicolas Delaby's avatar
Nicolas Delaby committed
1293 1294 1295
      return self.sendSyncModif(syncml_data_list, cmd_id_before_getsyncmldata,
                                subscriber, domain, xml_confirmation_list,
                                remote_xml, xml, has_status_list,
1296
                                has_response)
1297

1298 1299 1300 1301 1302 1303 1304
  def deleteRemainObjectList(self, domain, subscriber):
    """
    This method allow deletion on not synchronised Objects at the end of Synchronisation session.
    Usefull only after reseting in One Way Sync
    """
    object_list = domain.getObjectList()
    gid_list = [domain.getGidFromObject(x) for x in object_list]
1305 1306 1307 1308 1309 1310
    domain_path = domain.getPath()
    subscriber_path = subscriber.getPath()
    while len(gid_list):
      sliced_gid_list = [gid_list.pop() for i in gid_list[:self.MAX_OBJECTS]]
      #Split List Processing in activities
      self.activate(activity='SQLQueue',
Nicolas Delaby's avatar
Nicolas Delaby committed
1311 1312
                    tag=domain.getId(),
                    priority=self.PRIORITY).activateDeleteRemainObjectList(domain_path,
1313 1314 1315 1316 1317 1318 1319 1320 1321
                                                                       subscriber_path,
                                                                       sliced_gid_list)

  def activateDeleteRemainObjectList(self, domain_path, subscriber_path, gid_list):
    """
    Execute Deletion in Activities
    """
    domain = self.unrestrictedTraverse(domain_path)
    subscriber = self.unrestrictedTraverse(subscriber_path)
1322 1323 1324 1325
    destination = self.unrestrictedTraverse(domain.getDestinationPath())
    conduit_name = subscriber.getConduit()
    conduit = self.getConduitByName(conduit_name)
    for gid in gid_list:
1326
      if subscriber.getSignatureFromGid(gid) is None:
1327 1328 1329
        object_id = b16decode(gid)
        conduit.deleteObject(object=destination, object_id=object_id)

1330
  def activateSyncModif(self, **kw):
1331 1332 1333
    domain = self.unrestrictedTraverse(kw['domain_relative_url'])
    subscriber = self.unrestrictedTraverse(kw['subscriber_relative_url'])
    conduit = subscriber.getConduit()
1334 1335
    result = self.getSyncMLData(domain=domain, subscriber=subscriber,
                                conduit=conduit, max=self.MAX_OBJECTS, **kw)
Nicolas Delaby's avatar
Nicolas Delaby committed
1336
    syncml_data_list = result['syncml_data_list']
1337
    cmd_id = result['cmd_id']
Nicolas Delaby's avatar
Nicolas Delaby committed
1338
    kw['syncml_data_list'] = syncml_data_list
1339
    kw['cmd_id'] = cmd_id
1340 1341
    finished = result['finished']
    if not finished:
1342
      domain.activate(activity='SQLQueue',
Nicolas Delaby's avatar
Nicolas Delaby committed
1343 1344
                      tag=domain.getId(),
                      priority=self.PRIORITY).activateSyncModif(**kw)
1345 1346 1347
    else:
      cmd_id = result['cmd_id']
      cmd_id_before_getsyncmldata = kw['cmd_id_before_getsyncmldata']
1348
      remote_xml = etree.XML(kw['remote_xml'], parser=parser)
Nicolas Delaby's avatar
Nicolas Delaby committed
1349 1350
      xml_tree = etree.XML(kw['xml_tree'], parser=parser)
      xml_confirmation_list = kw['xml_confirmation_list']
1351 1352
      has_status_list = kw['has_status_list']
      has_response = kw['has_response']
1353
      return self.sendSyncModif(
Nicolas Delaby's avatar
Nicolas Delaby committed
1354
                        syncml_data_list,
1355 1356 1357
                        cmd_id_before_getsyncmldata,
                        subscriber,
                        domain,
Nicolas Delaby's avatar
Nicolas Delaby committed
1358
                        xml_confirmation_list,
1359
                        remote_xml,
Nicolas Delaby's avatar
Nicolas Delaby committed
1360
                        xml_tree,
1361 1362
                        has_status_list,
                        has_response)
1363

Nicolas Delaby's avatar
Nicolas Delaby committed
1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382
  def sendSyncModif(self, syncml_data_list, cmd_id_before_getsyncmldata,
                    subscriber, domain, xml_confirmation_list, remote_xml,
                    xml_tree, has_status_list, has_response):
    sync_body = xml_tree.find('SyncBody')
    if syncml_data_list:
      sync_node = SubElement(sync_body, 'Sync')
      cmd_id_node = SubElement(sync_node, 'CmdID')
      cmd_id_node.text = '%s' % cmd_id_before_getsyncmldata
      target_uri = subscriber.getTargetURI()
      if target_uri:
        sync_node.append(E.Target(E.LocURI(target_uri)))
      source_uri = subscriber.getSourceURI()
      if source_uri:
        sync_node.append(E.Source(E.LocURI(source_uri)))
      for syncml_data in syncml_data_list:
        sync_node.append(etree.XML(syncml_data, parser=parser))
    sync_body.extend(xml_confirmation_list)
    sync_body.append(Element('Final'))
    xml_string = etree.tostring(xml_tree, encoding='utf-8', pretty_print=True)
1383

1384
    if domain.domain_type == self.PUB: # We always reply
Nicolas Delaby's avatar
Nicolas Delaby committed
1385
      subscriber.setLastSentMessage(xml_string)
1386 1387 1388 1389
      self.sendResponse(
                from_url=domain.publication_url,
                to_url=subscriber.subscription_url,
                sync_id=domain.getTitle(),
Nicolas Delaby's avatar
Nicolas Delaby committed
1390
                xml=xml_string,
1391 1392
                domain=domain,
                content_type=domain.getSyncContentType())
Nicolas Delaby's avatar
Nicolas Delaby committed
1393 1394
      if not syncml_data_list:
        LOG('this is the end of the synchronisation session from PUB !!!', INFO, domain.getId())
Fabien Morin's avatar
Fabien Morin committed
1395 1396
        subscriber.setAuthenticated(False)
        domain.setAuthenticated(False)
1397 1398
      has_response = 1
    elif domain.domain_type == self.SUB:
Nicolas Delaby's avatar
Nicolas Delaby committed
1399 1400
      if self.checkAlert(remote_xml) or xml_confirmation_list or syncml_data_list:
        subscriber.setLastSentMessage(xml_string)
1401 1402 1403 1404
        self.sendResponse(
                  from_url=domain.subscription_url,
                  to_url=domain.publication_url,
                  sync_id=domain.getTitle(),
Nicolas Delaby's avatar
Nicolas Delaby committed
1405
                  xml=xml_string, domain=domain,
1406
                  content_type=domain.getSyncContentType())
1407
        has_response = 1
Fabien Morin's avatar
Fabien Morin committed
1408
      else:
1409 1410
        if domain.isOneWayFromServer():
          self.deleteRemainObjectList(domain, subscriber)
Nicolas Delaby's avatar
Nicolas Delaby committed
1411
        LOG('this is the end of the synchronisation session from SUB !!!', INFO, domain.getId())
Fabien Morin's avatar
Fabien Morin committed
1412
        domain.setAuthenticated(False)
Nicolas Delaby's avatar
Nicolas Delaby committed
1413
    return {'has_response':has_response, 'xml':xml_string}
1414 1415 1416 1417 1418

  def xml2wbxml(self, xml):
    """
    convert xml string to wbxml using a temporary file
    """
1419
    #LOG('xml2wbxml starting ...', DEBUG, '')
1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433
    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
    """
1434
    #LOG('wbxml2xml starting ...', DEBUG, '')
1435 1436 1437 1438 1439 1440 1441 1442 1443
    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
1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459

  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:
1460
      if isinstance(xml_client, (str, unicode)):
1461
        xml_client = etree.XML(xml_client, parser=parser)
1462
      if xml_client.tag != "SyncML":
1463 1464 1465 1466
        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
1467 1468
      client_header = xml_client[0]
      if client_header.tag != "SyncHdr":
1469 1470 1471 1472 1473
        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)
1474
      if subscriber is None:
Nicolas Delaby's avatar
Nicolas Delaby committed
1475
        subscriber = Subscriber(publication.generateNewId(), subscription_url)
1476 1477 1478
        subscriber.setXMLMapping(publication.getXMLMapping())
        subscriber.setConduit(publication.getConduit())
        publication.addSubscriber(subscriber)
1479
        subscriber = subscriber.__of__(publication)
1480
        # first synchronization
Nicolas Delaby's avatar
Nicolas Delaby committed
1481 1482 1483 1484
        result = self.PubSyncInit(publication=publication,
                                  xml_client=xml_client,
                                  subscriber=subscriber,
                                  sync_type=self.SLOW_SYNC)
1485 1486 1487 1488 1489
      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())
Nicolas Delaby's avatar
Nicolas Delaby committed
1490 1491 1492 1493
        result = self.PubSyncInit(publication=publication,
                                  xml_client=xml_client,
                                  subscriber=subscriber,
                                  sync_type=alert_code)
1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507
      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
Nicolas Delaby's avatar
Nicolas Delaby committed
1508 1509 1510 1511
      result = self.PubSyncInit(publication=publication,
                                xml_client=None,
                                subscriber=subscriber,
                                sync_type=self.TWO_WAY)
1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522
    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)
1523
    if msg is None and (subscription.getSubscriptionUrl()).find('file') >= 0:
1524
      msg = self.readResponse(sync_id=subscription.getSubscriptionUrl(),
1525
                              from_url=subscription.getSubscriptionUrl())
1526
    if msg is None:
1527 1528 1529
      response = self.SubSyncInit(subscription)
    else:
      xml_client = msg
1530
      if isinstance(xml_client, (str, unicode)):
1531
        xml_client = etree.XML(xml_client, parser=parser)
1532
        status_list = self.getSyncBodyStatusList(xml_client)
1533
        if status_list:
1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557
          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:
1558
          response = self.SubSyncModif(subscription, xml_client)
1559 1560 1561 1562 1563 1564 1565 1566

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

  def getActivityType(self, domain):
    if domain.getActivityEnabled():
1567
      return 'SQLQueue'
1568
    return 'RAMQueue'