##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#          Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################

import smtplib
from Products.ERP5SyncML.SyncCode import SyncCode
from Products.ERP5SyncML.Subscription import Signature
from AccessControl.SecurityManagement import newSecurityManager
from StringIO import StringIO
from ERP5Diff import ERP5Diff
from zLOG import LOG, INFO

try:
  from Ft.Xml import Parse
except ImportError:
  LOG('XMLSyncUtils', INFO, "Can't import Parse")
  class Parse:
    def __init__(self, *args, **kw):
      raise ImportError, "Sorry, it was not possible to import Ft library"

try:
      from base64 import b16encode, b16decode
except ImportError:
      from base64 import encodestring as b16encode, decodestring as b16decode
try:
  from Ft.Xml.Domlette import PrettyPrint
except ImportError:
  from xml.dom.ext import PrettyPrint
from xml.dom import minidom

class XMLSyncUtilsMixin(SyncCode):

  def SyncMLHeader(self, session_id, msg_id, target, source, target_name=None,
      source_name=None, dataCred=None, authentication_format='b64',
      authentication_type='syncml:auth-basic'):
    """
      Since the Header is always almost the same, this is the
      way to set one quickly.
    """
    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)
    if target_name not in (None, ''):
      xml('   <LocName>%s</LocName>\n' %target_name)
    xml('  </Target>\n')
    xml('  <Source>\n')
    xml('   <LocURI>%s</LocURI>\n' % source) 
    if source_name not in (None, ''):
      xml('   <LocName>%s</LocName>\n' % source_name)
    xml('  </Source>\n')
    if dataCred not in (None, ''):
      xml('  <Cred>\n')
      xml('   <Meta>\n')
      xml("    <Format xmlns='syncml:metinf'>%s</Format>\n" % authentication_format)
      xml("    <Type xmlns='syncml:metinf'>%s</Type>\n" % authentication_type)
      xml('   </Meta>\n')
      xml('   <Data>%s</Data>\n' % dataCred)
      xml('  </Cred>\n')
    xml(' </SyncHdr>\n')
    xml_a = ''.join(xml_list)
    return xml_a

  def SyncMLAlert(self, cmd_id, sync_code, target, source, last_anchor, 
      next_anchor):
    """
      Since the Alert section is always almost the same, this is the
      way to set one quickly.
    """
    xml_list = []
    xml = xml_list.append
    xml('  <Alert>\n')
    xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    xml('   <Data>%s</Data>\n' % sync_code)
    xml('   <Item>\n')
    xml('    <Target>\n')
    xml('     <LocURI>%s</LocURI>\n' % target)
    xml('    </Target>\n')
    xml('    <Source>\n')
    xml('     <LocURI>%s</LocURI>\n' % source)
    xml('    </Source>\n')
    xml('    <Meta>\n')
    xml('     <Anchor>\n')
    xml('      <Last>%s</Last>\n' % last_anchor)
    xml('      <Next>%s</Next>\n' % next_anchor)
    xml('     </Anchor>\n')
    xml('    </Meta>\n')
    xml('   </Item>\n')
    xml('  </Alert>\n')
    xml_a = ''.join(xml_list)
    return xml_a

  def SyncMLStatus(self, remote_xml, data_code, cmd_id, next_anchor, 
      subscription=None):
    """
    return a status bloc with all status corresponding to the syncml
    commands in remote_xml
    """

    #list of element in the SyncBody bloc
    syncbody_element_list = remote_xml.xpath('//SyncBody/*')
    message_id = self.getMessageId(remote_xml)
    xml_list = []
    xml = xml_list.append

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

        target_ref = syncbody_element.xpath('string(.//Target/LocURI)').encode('utf-8')
        if target_ref not in (None, ''):
          xml('   <TargetRef>%s</TargetRef>\n' % target_ref )
        source_ref = syncbody_element.xpath('string(.//Source/LocURI)').encode('utf-8')
        if source_ref not in (None, ''):
          xml('   <SourceRef>%s</SourceRef>\n' % source_ref )
        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')
        syncml_result = self.SyncMLPut(
                                  cmd_id,
                                  subscription,
                                  markup='Results',
                                  cmd_ref=cmd_ref,
                                  message_id=self.getMessageId(remote_xml))
        xml(syncml_result)
        cmd_id += 1
    xml_a = ''.join(xml_list)
    return {'xml':xml_a, 'cmd_id':cmd_id}

  def SyncMLConfirmation(self, cmd_id=None, target_ref=None, cmd=None,
      sync_code=None, msg_ref=None, cmd_ref=None, source_ref=None,
      remote_xml=None):
    """
    This is used in order to confirm that an object was correctly
    synchronized
    """
    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')

    xml_list = []
    xml = xml_list.append
    xml('  <Status>\n')
    #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)
    xml('  </Status>\n')
    xml_a = ''.join(xml_list)
    return xml_a

  def SyncMLChal(self, cmd_id, cmd, target_ref, source_ref, auth_format,
      auth_type, data_code):
    """
    This is used in order to ask crendentials
    """
    xml_list = []
    xml = xml_list.append
    xml('  <Status>\n')
    xml('   <CmdID>%s</CmdID>\n' % cmd_id)
    xml('   <MsgRef>1</MsgRef>\n')
    xml('   <CmdRef>0</CmdRef>\n')
    xml('   <Cmd>%s</Cmd>\n' % cmd)
    xml('   <TargetRef>%s</TargetRef>\n' % target_ref)
    xml('   <SourceRef>%s</SourceRef>\n' % source_ref)
    xml('   <Chal>\n')
    xml('    <Meta>\n')
    xml("     <Format xmlns='syncml:metinf'>%s</Format>\n" % auth_format)
    xml("     <Type xmlns='syncml:metinf'>%s</Type>\n" % auth_type)
    xml('    </Meta>\n')
    xml('   </Chal>\n')
    xml('   <Data>%s</Data>\n' % str(data_code))
    xml('  </Status>\n')
    xml_a = ''.join(xml_list)
    return xml_a

  def SyncMLPut(self, cmd_id, subscription, markup='Put', cmd_ref=None,
      message_id=None):
    """
    this is used to inform the server of the CTType version supported
    but if the server use it to respond to a Get request, it's a <Result> markup
    instead of <Put>
    """
    conduit_name = subscription.getConduit()
    conduit = self.getConduitByName(conduit_name)
    #if the conduit support the SyncMLPut :
    if hasattr(conduit, 'getCapabilitiesCTTypeList') and \
        hasattr(conduit, 'getCapabilitiesVerCTList') and \
        hasattr(conduit, 'getPreferedCapabilitieVerCT'):
      xml_list = []
      xml = xml_list.append
      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)
      xml_a = ''.join(xml_list)
      return xml_a
    return ''


  def sendMail(self, fromaddr, toaddr, id_sync, msg):
    """
      Send a message via email
      - sync_object : this is a publication or a subscription
      - message : what we want to send
    """
    header = "Subject: %s\n" % id_sync
    header += "To: %s\n\n" % toaddr
    msg = header + msg
    #LOG('SubSendMail', DEBUG,'from: %s, to: %s' % (fromaddr,toaddr))
    server = smtplib.SMTP('localhost')
    server.sendmail(fromaddr, toaddr, msg)
    # if we want to send the email to someone else (debugging)
    #server.sendmail(fromaddr, "seb@localhost", msg)
    server.quit()

  def addXMLObject(self, cmd_id=0, object=None, xml_string=None,
                  more_data=0, gid=None, media_type=None):
    """
      Add an object with the SyncML protocol
    """
    xml_list = []
    xml = xml_list.append
    xml('   <Add>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
    xml('    <Meta>\n')
    xml('     <Type>%s</Type>\n' % media_type)
    xml('    </Meta>\n')
    xml('    <Item>\n')
    xml('     <Source>\n')
    xml('      <LocURI>%s</LocURI>\n' % gid)
    xml('     </Source>\n')
    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')
    if more_data == 1:
      xml('     <MoreData/>\n')
    xml('    </Item>\n')
    xml('   </Add>\n')
    xml_a = ''.join(xml_list)
    return xml_a

  def deleteXMLObject(self, cmd_id=0, object_gid=None, rid=None, xml_object=''):
    """
      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')
    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')
    xml('    </Item>\n')
    xml('   </Delete>\n')
    xml_a = ''.join(xml_list)
    return xml_a

  def replaceXMLObject(self, cmd_id=0, object=None, xml_string=None,
                       more_data=0, gid=None, rid=None, media_type=None):
    """
      Replace an object with the SyncML protocol
    """
    xml_list = []
    xml = xml_list.append
    xml('   <Replace>\n')
    xml('    <CmdID>%s</CmdID>\n' % cmd_id)
    xml('    <Meta>\n')
    xml('     <Type>%s</Type>\n' % media_type)
    xml('    </Meta>\n')
    xml('    <Item>\n')
    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')
    xml('     <Data>')
    xml(xml_string)
    xml('     </Data>\n')
    if more_data == 1:
      xml('     <MoreData/>\n')
    xml('    </Item>\n')
    xml('   </Replace>\n')
    xml_a = ''.join(xml_list)
    return xml_a

  def getXupdateObject(self, object_xml=None, old_xml=None):
    """
    Generate the xupdate with the new object and the old xml
    """
    erp5diff = ERP5Diff()
    erp5diff.compare(old_xml, object_xml)
    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')
      attr_version.value = '1.0'
      erp5diff._result.documentElement.setAttributeNodeNS(attr_version)
      attr_ns = erp5diff._result.createAttributeNS(None, 'xmlns:xupdate')
      attr_ns.value = 'http://www.xmldb.org/xupdate'
      erp5diff._result.documentElement.setAttributeNodeNS(attr_ns)
      xupdate = erp5diff._result.toxml('utf-8')
    else:
      #Upper version of ERP5Diff produce valid XML.
      xupdate = erp5diff.outputString()
    #omit xml declaration
    xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
    return xupdate

  def getSessionId(self, xml):
    """
    We will retrieve the session id of the message
    """
    session_id = 0
    session_id = xml.xpath('string(/SyncML/SyncHdr/SessionID)')
    session_id = int(session_id)
    return session_id

  def getMessageId(self, xml):
    """
    We will retrieve the message id of the message
    """
    message_id = 0
    message_id = xml.xpath('string(/SyncML/SyncHdr/MsgID)')
    message_id = int(message_id)
    return message_id

  def getTarget(self, xml):
    """
    return the target in the SyncHdr section
    """
    url = ''
    url = xml.xpath('string(/SyncML/SyncHdr/Target/LocURI)')
    url = url.encode('utf-8')
    return url

  def getAlertLastAnchor(self, xml_stream):
    """
      Return the value of the last anchor, in the
      alert section of the xml_stream
    """
    last_anchor = xml_stream.xpath('string(.//Alert/Item/Meta/Anchor/Last)')
    last_anchor = last_anchor.encode('utf-8')
    return last_anchor

  def getAlertNextAnchor(self, xml_stream):
    """
      Return the value of the next anchor, in the
      alert section of the xml_stream
    """
    next_anchor = xml_stream.xpath('string(.//Alert/Item/Meta/Anchor/Next)')
    next_anchor = next_anchor.encode('utf-8')
    return next_anchor

  def getSourceURI(self, xml):
    """
    return the source uri of the data base
    """
    source_uri = xml.xpath('string(//SyncBody/Alert/Item/Source/LocURI)')
    if isinstance(source_uri, unicode):
      source_uri = source_uri.encode('utf-8')
    return source_uri

  def getTargetURI(self, xml):
    """
    return the target uri of the data base
    """
    target_uri = xml.xpath('string(//SyncBody/Alert/Item/Target/LocURI)')
    if isinstance(target_uri, unicode):
      target_uri = target_uri.encode('utf-8')
    return target_uri

  def getSubscriptionUrlFromXML(self, xml):
    """
    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

  def getStatusTarget(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
    status = xml.xpath('string(TargetRef)')
    if isinstance(status, unicode):
      status = status.encode('utf-8')
    return status

  def getStatusCode(self, xml):
    """
      Return the value of the alert code inside the xml_stream
    """
    status_code = xml.xpath('string(Data)')
    if status_code not in ('', None, []):
      return int(status_code)
    return None

  def getStatusCommand(self, xml):
    """
      Return the value of the command inside the xml_stream
    """
    cmd = None
    if xml.nodeName=='Status':
      cmd = xml.xpath('string(//Status/Cmd)')
      if isinstance(cmd, unicode):
        cmd = cmd.encode('utf-8')
    return cmd

  def getCred(self, xml):
    """
      return the credential information : type, format and data
    """
    format=''
    type=''
    data=''

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

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

  def checkCred(self, xml_stream):
    """
      Check if there's a Cred section in the xml_stream
    """
    return xml_stream.xpath('string(SyncML/SyncHdr/Cred)') not in ('', None, [])

  def getChal(self, xml):
    """
      return the chalenge information : format and type
    """
    format=None
    type=None

    first_node = xml.childNodes[0]
    format = first_node.xpath("string(//*[local-name() = 'Format'])")
    type = first_node.xpath("string(//*[local-name() = 'Type'])")

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

  def checkChal(self, xml_stream):
    """
      Check if there's a Chal section in the xml_stream
    """
    return xml_stream.xpath('string(SyncML/SyncBody/Status/Chal)') not in ('', None, [])

  def checkMap(self, xml_stream):
    """
      Check if there's a Map section in the xml_stream
    """
    return xml_stream.xpath('string(SyncML/SyncBody/Map)') not in ('', None, [])

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

  def getAlertCodeFromXML(self, xml_stream):
    """
      Return the value of the alert code inside the full syncml message
    """
    alert_code = xml_stream.xpath('string(SyncML/SyncBody/Alert/Data)')
    if alert_code not in (None, ''):
      return int(alert_code)
    else:
      return None

  def checkAlert(self, xml_stream):
    """
      Check if there's an Alert section in the xml_stream
    """
    return xml_stream.xpath('string(SyncML/SyncBody/Alert)') not in ('', None, [])

  def checkSync(self, xml_stream):
    """
      Check if there's an Sync section in the xml_xtream
    """
    return xml_stream.xpath('string(SyncML/SyncBody/Sync)') not in ('', None, [])

  def checkStatus(self, xml_stream):
    """
      Check if there's a Status section in the xml_xtream
    """
    return xml_stream.xpath('string(SyncML/SyncBody/Status)') not in ('', None, [])

  def getSyncActionList(self, xml_stream):
    """
    return the list of the action (could be "add", "replace", "delete").
    """
    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

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

  def getDataSubNode(self, action):
    """
      Return the node starting with <object....> of the action
    """
    if action.xpath('.//Item/Data') not in ([], None):
      data_node = action.xpath('.//Item/Data')[0]
      if data_node.hasChildNodes():
        return data_node.childNodes[0]
    return None

  def getPartialData(self, action):
    """
      Return the node starting with <object....> of the action
    """
    comment_list = action.xpath('.//Item/Data[comment()]')
    if comment_list != []:
      return comment_list[0].childNodes[0].data.encode('utf-8')
    return None

  def getActionId(self, action):
    """
      Return the rid of the object described by the action
    """
    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

  def checkActionMoreData(self, action):
    """
      Return the rid of the object described by the action
    """
    return action.xpath('Item/MoreData') not in ([],None)

  def getActionType(self, action):
    """
      Return the type of the object described by the action
    """
    action_type = action.xpath('string(Meta/Type)')
    if isinstance(action_type, unicode):
      action_type = action_type.encode('utf-8')
    return action_type

  def getElementNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
    return node.xpath('*')

  def getTextNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
    subnode_list = []
    for subnode in node.childNodes or []:
      if subnode.nodeType == subnode.TEXT_NODE:
        subnode_list += [subnode]
    return subnode_list

  def getAttributeNodeList(self, node):
    """
      Return childNodes that are ElementNode
    """
    return node.xpath('@*')

  def getSyncMLData(self, domain=None, remote_xml=None, cmd_id=0,
                    subscriber=None, xml_confirmation=None, conduit=None,
                    max=None, **kw):
    """
    This generate the syncml data message. This returns a string
    with all modification made locally (ie replace, add ,delete...)

    if object is not None, this usually means we want to set the
    actual xupdate on the signature.
    """
    #LOG('getSyncMLData starting...', DEBUG, domain.getId())
    if isinstance(conduit, str):
      conduit = self.getConduitByName(conduit)
    local_gid_list = []
    syncml_data = kw.get('syncml_data','')
    result = {'finished':1}
    if isinstance(remote_xml, str) or isinstance(remote_xml, unicode):
      remote_xml = Parse(remote_xml)
    if domain.isOneWayFromServer():
      #Do not set object_path_list, subscriber send nothing
      subscriber.setRemainingObjectPathList([])
    elif subscriber.getRemainingObjectPathList() is None:
      object_list = domain.getObjectList()
      object_path_list = [x.getPhysicalPath() for x in object_list]
      subscriber.setRemainingObjectPathList(object_path_list)
      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:
          #LOG('getSyncMLData :', DEBUG, 'object:%s,  objectTitle:%s, local_gid_list:%s' % (object, object.getTitle(), local_gid_list))
          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))
          #LOG('getSyncMLData :', DEBUG,'gid_not_encoded_list:%s, local_gid_list:%s, gid:%s' % (gid_not_encoded_list, local_gid_list, gid))
      else:
        local_gid_list = [domain.getGidFromObject(x) for x in object_list]

      # Objects to remove
      #LOG('getSyncMLData remove object to remove ...', DEBUG, '')
      object_gid_deleted = []
      for object_gid in subscriber.getGidList():
        if object_gid not in local_gid_list:
          # This is an object to remove
          signature = subscriber.getSignatureFromGid(object_gid)
          if signature.getStatus() != self.PARTIAL:
            # If partial, then we have a signature but no local object
            xml_object = signature.getXML()
            if xml_object is not None: # This prevent to delete an object that
                                       # we were not able to create
              rid = signature.getRid()
              object_gid_deleted.append(object_gid)
              syncml_data += self.deleteXMLObject(
                                      xml_object=signature.getXML() or '',
                                      object_gid=object_gid,
                                      rid=rid,
                                      cmd_id=cmd_id)
              cmd_id += 1
      #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)
    local_gid_list = []
    loop = 0
    for object_path in subscriber.getRemainingObjectPathList():
      if max is not None and loop >= max:
        result['finished'] = 0
        break
      #LOG('getSyncMLData object_path', INFO, object_path)
      object = self.unrestrictedTraverse(object_path)
      status = self.SENT
      object_gid = domain.getGidFromObject(object)
      if object_gid in ('', None):
        continue
      local_gid_list += [object_gid]
      force = 0
      if syncml_data.count('\n') < self.MAX_LINES and not \
          object.id.startswith('.'):
        # If not we have to cut
        #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))

        signature = subscriber.getSignatureFromGid(object_gid)
        ## Here we first check if the object was modified or not by looking at dates
        #if signature is not None:
          #LOG('getSyncMLData', DEBUG, 'signature.getStatus: %s' % signature.getStatus())
        status = self.SENT
        more_data=0
        # For the case it was never synchronized, we have to send everything
        if signature is not None and signature.getXMLMapping() is None:
          pass
        elif signature is None or (signature.getXML() is None and \
            signature.getStatus() != self.PARTIAL) or \
            self.getAlertCodeFromXML(remote_xml) == self.SLOW_SYNC:
          #LOG('getSyncMLData', DEBUG, 'Current object.getPath: %s' % object.getPath())
          xml_object = domain.getXMLFromObject(object)
          xml_string = xml_object
          if isinstance(xml_string, unicode):
            xml_string = xml_object.encode('utf-8')
          gid = subscriber.getGidFromObject(object)
          signature = Signature(id=gid, object=object).__of__(subscriber)
          signature.setTempXML(xml_object)
          if xml_string.count('\n') > self.MAX_LINES:
            if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
              xml_string = xml_string.replace('--','@-@@-@')
            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
            #LOG('getSyncMLData', DEBUG, 'setPartialXML with: %s' % str(rest_string))
            signature.setPartialXML(rest_string)
            status = self.PARTIAL
            signature.setAction('Add')
            xml_string = '<!--' + short_string + '-->'
          gid = signature.getRid()#in fisrt, we try with rid if there is one
          if gid is None:
            gid = signature.getGid()
          syncml_data += self.addXMLObject(
                                  cmd_id=cmd_id,
                                  object=object,
                                  gid=gid,
                                  xml_string=xml_string,
                                  more_data=more_data,
                                  media_type=subscriber.getMediaType())
          cmd_id += 1
          signature.setStatus(status)
          subscriber.addSignature(signature)
        elif signature.getStatus() == self.NOT_SYNCHRONIZED \
            or signature.getStatus() == self.PUB_CONFLICT_MERGE:
          # We don't have synchronized this object yet
          xml_object = domain.getXMLFromObject(object)
          #LOG('getSyncMLData', DEBUG, 'checkMD5: %s' % str(signature.checkMD5(xml_object)))
          #LOG('getSyncMLData', DEBUG, 'getStatus: %s' % str(signature.getStatus()))
          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')
          set_synchronized = 1
          if not signature.checkMD5(xml_object):
            set_synchronized = 0
            # This object has changed on this side, we have to generate some xmldiff
            if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']:
              xml_string = self.getXupdateObject(xml_object, signature.getXML())
            else: #if there is no xml, we re-send all the object
              xml_string = xml_object
            if xml_string.count('\n') > self.MAX_LINES:
              # This make comment fails, so we need to replace
              if xml_string.find('--') >= 0:
                xml_string = xml_string.replace('--', '@-@@-@')
              i = 0
              more_data = 1
              short_string_list = []
              short_string_list_ap = short_string_list.append
              rest_string = xml_string
              while i < self.MAX_LINES:
                short_string_list_ap(rest_string[:rest_string.find('\n')+1])
                rest_string = xml_string[len(''.join(short_string_list)):]
                i += 1
              signature.setPartialXML(rest_string)
              status = self.PARTIAL
              signature.setAction('Replace')
              xml_string = '<!--' + ''.join(short_string_list) + '-->'
            signature.setStatus(status)
            if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
              xml_string = xml_object
            rid = signature.getRid()#in fisrt, we try with rid if there is one
            gid = signature.getGid()
            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())
            cmd_id += 1
            signature.setTempXML(xml_object)
          # Now we can apply the xupdate from the subscriber
          subscriber_xupdate = signature.getSubscriberXupdate()
          #LOG('getSyncMLData subscriber_xupdate', DEBUG, subscriber_xupdate)
          if subscriber_xupdate is not None:
            old_xml = signature.getXML()
            conduit.updateNode(
                        xml=subscriber_xupdate,
                        object=object,
                        previous_xml=old_xml,
                        force=(domain.getDomainType() == self.SUB),
                        simulate=0)
            xml_object = domain.getXMLFromObject(object)
            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
            signature.setStatus(self.SYNCHRONIZED)
        elif signature.getStatus() == self.PUB_CONFLICT_CLIENT_WIN:
          # We have decided to apply the update
          # XXX previous_xml will be geXML instead of getTempXML because
          # some modification was already made and the update
          # may not apply correctly
          xml_update = signature.getPartialXML()
          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')
          signature.setStatus(self.SYNCHRONIZED)
        elif signature.getStatus() == self.PARTIAL:
          xml_string = signature.getPartialXML()
          if xml_string.count('\n') > self.MAX_LINES:
            i = 0
            more_data=1
            short_string_list = []
            short_string_list_ap = short_string_list.append
            rest_string = xml_string
            while i < self.MAX_LINES:
              short_string_list_ap(rest_string[:rest_string.find('\n')+1])
              rest_string = xml_string[len(''.join(short_string_list)):]
              i += 1
            signature.setPartialXML(rest_string)
            xml_string = ''.join(short_string_list)
            status = self.PARTIAL
          if xml_string.find('--') >= 0: # This make comment fails, so we need to replace
            xml_string = xml_string.replace('--','@-@@-@')
          xml_string = '<!--' + xml_string + '-->'
          signature.setStatus(status)
          if(subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']):
            xml_string = domain.getXMLFromObject(object)
          if signature.getAction() == 'Replace':
            rid = signature.getRid()#in fisrt, we try with rid if there is one
            gid = signature.getGid()
            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':
            gid = signature.getRid()#in fisrt, we try with rid if there is one
            if gid == None:
              gid = signature.getGid()
            syncml_data += self.addXMLObject(
                                  cmd_id=cmd_id,
                                  object=object,
                                  gid=gid,
                                  xml_string=xml_string,
                                  more_data=more_data,
                                  media_type=subscriber.getMediaType())
        if not more_data:
          subscriber.removeRemainingObjectPath(object_path)
      else:
        result['finished'] = 1
        break
      loop += 1
    result['syncml_data'] = syncml_data
    result['xml_confirmation'] = xml_confirmation
    result['cmd_id'] = cmd_id
    return result

  def applyActionList(self, domain=None, subscriber=None, cmd_id=0,
                      remote_xml=None, conduit=None, simulate=0):
    """
    This just look to a list of action to do, then id applies
    each action one by one, thanks to a conduit
    """
    xml_confirmation = ''
    has_next_action = 0
    gid_from_xml_list = []
    destination = self.unrestrictedTraverse(domain.getDestinationPath())
    #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))
    for action in self.getSyncActionList(remote_xml):
      if isinstance(action, unicode):
        action = action.encode('utf-8')
      conflict_list = []
      status_code = self.SUCCESS
      # Thirst we have to check the kind of action it is
      partial_data = self.getPartialData(action)
      rid = self.getActionId(action)
      if action.nodeName != 'Delete':
        if hasattr(conduit, 'getGidFromXML') and \
            conduit.getGidFromXML(self.getDataText(action),
            gid_from_xml_list) not in ('', None):
          gid = conduit.getGidFromXML(self.getDataText(action),
              gid_from_xml_list)
          gid_from_xml_list.append(gid)
          gid = b16encode(gid)
        else:
          gid=rid
      else:
        gid=rid
      object_id = domain.generateNewIdWithGenerator(object=destination, gid=gid)
      signature = subscriber.getSignatureFromGid(gid)
      if signature is not None and rid != gid:
        #in this case, the object was created on another subscriber than erp5
        # and we should save it's remote id
        signature.setRid(rid)
      #LOG('gid == rid ?', DEBUG, 'gid=%s, rid=%s' % (gid, rid))
      object = subscriber.getObjectFromGid(gid)
      if object is 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, '')
        object = subscriber.getObjectFromRid(rid)
        signature = subscriber.getSignatureFromRid(rid)
        if signature not in ('', None):
          gid = signature.getId()
      #LOG('applyActionList subscriber.getObjectFromGid %s' % gid, DEBUG, object)
      if signature is None:
        #LOG('applyActionList, signature is None', DEBUG, signature)
        if gid == rid:
          signature = Signature(id=gid, status=self.NOT_SYNCHRONIZED,
              object=object).__of__(subscriber)
        else:
          signature = Signature(rid=rid, id=gid, status=self.NOT_SYNCHRONIZED,
              object=object).__of__(subscriber)
        signature.setObjectId(object_id)
        subscriber.addSignature(signature)
      force = signature.getForce()
      if self.checkActionMoreData(action) == 0:
        data_subnode = None
        if partial_data != None:
          signature_partial_xml = signature.getPartialXML()
          if signature_partial_xml is not None:
            data_subnode = signature.getPartialXML() + partial_data
          else:
            data_subnode = partial_data
          #LOG('applyActionList', DEBUG, 'data_subnode: %s' % data_subnode)
          if subscriber.getMediaType() == self.MEDIA_TYPE['TEXT_XML']:
            data_subnode = Parse(data_subnode)
            data_subnode = data_subnode.childNodes[0] # Because we just created a new xml
          # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
        else:
          if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
            data_subnode = self.getDataText(action)
          else:
            data_subnode = self.getDataSubNode(action)
        if action.nodeName == 'Add':
          # Then store the xml of this new subobject
          reset = 0
          if object is None:
            add_data = conduit.addNode(xml=data_subnode,
                                       object=destination,
                                       object_id=object_id)
            if add_data['conflict_list'] not in ('', None, []):
              conflict_list += add_data['conflict_list']
            # Retrieve directly the object from addNode
            object = add_data['object']
            if object is not None:
              signature.setPath(object.getPhysicalPath())
              signature.setObjectId(object.getId())
          else:
            reset = 1
            #Object was retrieve but need to be updated without recreated
            #usefull when an object is only deleted by workflow.
            if data_subnode is not None:
              if type(data_subnode) != type(''):
                string_io = StringIO()
                PrettyPrint(data_subnode, stream=string_io)
                xml_string = string_io.getvalue()
              actual_xml = subscriber.getXMLFromObject(object = object, force=1)
              data_subnode = self.getXupdateObject(xml_string, actual_xml)
            conflict_list += conduit.updateNode(
                                        xml=data_subnode,
                                        object=object,
                                        previous_xml=signature.getXML(),
                                        force=force,
                                        simulate=simulate)
            xml_object = domain.getXMLFromObject(object)
            signature.setTempXML(xml_object)
          if object is not None:
            #LOG('applyActionList', DEBUG, 'addNode, found the object')
            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)
            signature.setStatus(self.SYNCHRONIZED)
            #signature.setId(object.getId())
            signature.setPath(object.getPhysicalPath())
            signature.setXML(xml_object)
            xml_confirmation += self.SyncMLConfirmation(
                cmd_id=cmd_id,
                cmd='Add',
                sync_code=self.ITEM_ADDED,
                remote_xml=action)
            cmd_id +=1
        elif action.nodeName == 'Replace':
          #LOG('applyActionList', DEBUG, 'object: %s will be updated...' % str(object))
          if object is not None:
            #LOG('applyActionList', DEBUG, 'object: %s will be updated...' % object.id)
            signature = subscriber.getSignatureFromGid(gid)
            if signature is None:
              signature = subscriber.getSignatureFromRid(gid)
            #LOG('applyActionList', DEBUG, 'previous signature: %s' % str(signature))
            previous_xml = signature.getXML()
            conflict_list += conduit.updateNode(
                                        xml=data_subnode,
                                        object=object,
                                        previous_xml=signature.getXML(),
                                        force=force,
                                        simulate=simulate)
            xml_object = domain.getXMLFromObject(object)
            signature.setTempXML(xml_object)
            if conflict_list != []:
              status_code = self.CONFLICT
              signature.setStatus(self.CONFLICT)
              signature.setConflictList(signature.getConflictList() \
                  + conflict_list)
              string_io = StringIO()
              PrettyPrint(data_subnode, stream=string_io)
              data_subnode_string = string_io.getvalue()
              signature.setPartialXML(data_subnode_string)
            elif not simulate:
              signature.setStatus(self.SYNCHRONIZED)
            xml_confirmation += self.SyncMLConfirmation(
                                              cmd_id=cmd_id,
                                              cmd='Replace',
                                              sync_code=status_code,
                                              remote_xml=action)
            cmd_id +=1
            if simulate:
              # This means we are on the publisher side and we want to store
              # the xupdate from the subscriber and we also want to generate
              # the current xupdate from the last synchronization
              string_io = StringIO()
              PrettyPrint(data_subnode, stream=string_io)
              data_subnode_string = string_io.getvalue()
              #LOG('applyActionList, subscriber_xupdate:', TRACE, data_subnode_string)
              signature.setSubscriberXupdate(data_subnode_string)

        elif action.nodeName == 'Delete':
          object_id = signature.getId()
          #LOG('applyActionList Delete on : ', DEBUG, (signature.getId(), subscriber.getObjectFromGid(object_id)))
          if subscriber.getMediaType() != self.MEDIA_TYPE['TEXT_XML']:
            data_subnode = self.getDataText(action)
          else:
            data_subnode = self.getDataSubNode(action)
          #LOG('applyActionList, object gid to delete :', 0, subscriber.getObjectFromGid(object_id))
          if subscriber.getObjectFromGid(object_id) not in (None, ''):
          #if the object exist:
            conduit.deleteNode(
                        xml=data_subnode,
                        object=destination,
                        object_id=subscriber.getObjectFromGid(object_id).getId())
            subscriber.delSignature(gid)
          xml_confirmation += self.SyncMLConfirmation(
                                            cmd_id=cmd_id,
                                            cmd='Delete',
                                            sync_code=status_code,
                                            remote_xml=action)
      else: # We want to retrieve more data
        signature.setStatus(self.PARTIAL)
        previous_partial = signature.getPartialXML() or ''
        previous_partial += partial_data
        #LOG('applyActionList', DEBUG, 'setPartialXML: %s' % str(previous_partial))
        signature.setPartialXML(previous_partial)
        #LOG('applyActionList', DEBUG, 'previous_partial: %s' % str(previous_partial))
        #LOG('applyActionList', DEBUG, 'waiting more data for :%s' % signature.getId())
        xml_confirmation += self.SyncMLConfirmation(
                                          cmd_id=cmd_id,
                                          cmd=action.nodeName,
                                          sync_code=self.WAITING_DATA,
                                          remote_xml=action)
      if conflict_list != [] and signature is not None:
        # We had a conflict
        signature.setStatus(self.CONFLICT)

    return (xml_confirmation, has_next_action, cmd_id)

  def applyStatusList(self, subscriber=None, remote_xml=None):
    """
    This read a list of status list (ie syncml confirmations).
    This method have to change status codes on signatures
    """
    status_list = self.getSyncBodyStatusList(remote_xml)
    has_status_list = 0
    destination_waiting_more_data = 0
    if status_list != []:
      for status in status_list:
        status_cmd = status['cmd']
        object_gid = status['source']
        if object_gid in ('', None, []):
          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:
            signature = subscriber.getSignatureFromGid(object_gid)
            if signature is None and \
            not(subscriber.getSynchronizeWithERP5Sites()):
              signature = subscriber.getSignatureFromRid(object_gid)
            if signature is not None:
              subscriber.delSignature(signature.getGid())
    return (destination_waiting_more_data, has_status_list)


class XMLSyncUtils(XMLSyncUtilsMixin):

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

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

  def 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

  def SyncModif(self, domain, remote_xml):
    """
    Modification Message, this is used after the first
    message in order to send modifications.
    Send the server modification, this happens after the Synchronization
    initialization
    """
    has_response = 0 #check if syncmodif replies to this messages
    cmd_id = 1 # specifies a SyncML message-unique command identifier
    #LOG('SyncModif', DEBUG, 'Starting... domain: %s' % domain.getId())
    first_node = remote_xml.childNodes[0]
    # Get informations from the header
    xml_header = first_node.childNodes[1]
    if xml_header.nodeName != "SyncHdr":
      LOG('SyncModif', INFO, 'This is not a SyncML Header')
      raise ValueError, "Sorry, This is not a SyncML Header"

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

    # We have to check if this message was not already, this can be dangerous
    # to update two times the same object
    message_id = self.getMessageId(remote_xml)
    correct_message = subscriber.checkCorrectRemoteMessageId(message_id)
    if not correct_message: # We need to send again the message
      LOG('SyncModif, no correct message:', INFO, "sending again...")
      last_xml = subscriber.getLastSentMessage()
      LOG("SyncModif last_xml :", INFO, last_xml)
      string_io = StringIO()
      PrettyPrint(remote_xml, stream=string_io)
      remote_xml = string_io.getvalue()
      LOG("SyncModif remote_xml :", INFO, remote_xml)
      if last_xml != '':
        has_response = 1
        if domain.domain_type == self.PUB: # We always reply
          self.sendResponse(
                    from_url=domain.publication_url,
                    to_url=subscriber.subscription_url,
                    sync_id=domain.getTitle(),
                    xml=last_xml,domain=domain,
                    content_type=domain.getSyncContentType())
        elif domain.domain_type == self.SUB:
          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}
    subscriber.setLastSentMessage('')

    # First apply the list of status codes
    (destination_waiting_more_data,has_status_list) = self.applyStatusList(
                                                      subscriber=subscriber,
                                                      remote_xml=remote_xml)

    alert_code = self.getAlertCodeFromXML(remote_xml)
    # Import the conduit and get it
    conduit = self.getConduitByName(subscriber.getConduit())
    # Then apply the list of actions
    (xml_confirmation, has_next_action, cmd_id) = self.applyActionList(
                                          cmd_id=cmd_id,
                                          domain=domain,
                                          subscriber=subscriber,
                                          remote_xml=remote_xml,
                                          conduit=conduit, simulate=simulate)
    xml_list = []
    xml = xml_list.append
    xml('<SyncML>\n')

    # syncml header
    if domain.domain_type == self.PUB:
      xml(self.SyncMLHeader(
                  subscriber.getSessionId(),
                  subscriber.incrementMessageId(),
                  subscriber.getSubscriptionUrl(),
                  domain.getPublicationUrl()))
    elif domain.domain_type == self.SUB:
      xml(self.SyncMLHeader(
                  domain.getSessionId(), domain.incrementMessageId(),
                  domain.getPublicationUrl(),
                  domain.getSubscriptionUrl()))
    # Add or replace objects
    syncml_data = ''

    # syncml body
    xml(' <SyncBody>\n')

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

    destination_url = ''
    # alert message if we want more data
    if destination_waiting_more_data == 1:
      xml(self.SyncMLAlert(
                    cmd_id,
                    self.WAITING_DATA,
                    subscriber.getTargetURI(),
                    subscriber.getSourceURI(),
                    subscriber.getLastAnchor(),
                    subscriber.getNextAnchor()))
    # Now we should send confirmations
    cmd_id_before_getsyncmldata = cmd_id
    cmd_id = cmd_id+1
    if domain.getActivityEnabled():
      #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()
      domain.activate(activity='SQLQueue',
                           tag=domain.getId()).activateSyncModif(
                      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 )
      return {'has_response':1, 'xml':''}
    else:
      result = self.getSyncMLData(domain=domain,
                             remote_xml=remote_xml,
                             subscriber=subscriber,
                             cmd_id=cmd_id,
                             xml_confirmation=xml_confirmation,
                             conduit=conduit,
                             max=None)
      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,
                                remote_xml, xml_list, has_status_list,
                                has_response)

  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]
    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',
                    tag=domain.getId()).activateDeleteRemainObjectList(domain_path,
                                                                       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)
    destination = self.unrestrictedTraverse(domain.getDestinationPath())
    conduit_name = subscriber.getConduit()
    conduit = self.getConduitByName(conduit_name)
    for gid in gid_list:
      if subscriber.getSignatureFromGid(gid) is None:
        object_id = b16decode(gid)
        conduit.deleteObject(object=destination, object_id=object_id)

  def activateSyncModif(self, **kw):
    domain = self.unrestrictedTraverse(kw['domain_relative_url'])
    subscriber = self.unrestrictedTraverse(kw['subscriber_relative_url'])
    conduit = subscriber.getConduit()
    result = self.getSyncMLData(
                        domain = domain,
                        subscriber = subscriber,
                        conduit = conduit,
                        max = self.MAX_OBJECTS,
                        **kw)
    syncml_data = result['syncml_data']
    cmd_id = result['cmd_id']
    kw['syncml_data'] = syncml_data
    kw['cmd_id'] = cmd_id
    finished = result['finished']
    if not finished:
      domain.activate(activity='SQLQueue',
                           tag=domain.getId()).activateSyncModif(**kw)
    else:
      xml_confirmation = result['xml_confirmation']
      cmd_id = result['cmd_id']
      cmd_id_before_getsyncmldata = kw['cmd_id_before_getsyncmldata']
      remote_xml = Parse(kw['remote_xml'])
      xml_list = kw['xml_list']
      has_status_list = kw['has_status_list']
      has_response = kw['has_response']
      return self.sendSyncModif(
                        syncml_data,
                        cmd_id_before_getsyncmldata,
                        subscriber,
                        domain,
                        xml_confirmation,
                        remote_xml,
                        xml_list,
                        has_status_list,
                        has_response)

  def sendSyncModif(self, syncml_data, cmd_id_before_getsyncmldata, subscriber,
                    domain, xml_confirmation, remote_xml, xml_list,
                    has_status_list, has_response):
    xml = xml_list.append
    if syncml_data != '':
      xml('  <Sync>\n')
      xml('   <CmdID>%s</CmdID>\n' % cmd_id_before_getsyncmldata)
      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')
      xml(syncml_data)
      xml('  </Sync>\n')
    xml(xml_confirmation)
    xml('  <Final/>\n')
    xml(' </SyncBody>\n')
    xml('</SyncML>\n')
    xml_a = ''.join(xml_list)

    if domain.domain_type == self.PUB: # We always reply
      subscriber.setLastSentMessage(xml_a)
      self.sendResponse(
                from_url=domain.publication_url,
                to_url=subscriber.subscription_url,
                sync_id=domain.getTitle(),
                xml=xml_a,
                domain=domain,
                content_type=domain.getSyncContentType())
      if syncml_data == '':
        LOG('this is the end of the synchronisation session !!!', INFO, domain.getId())
        subscriber.setAuthenticated(False)
        domain.setAuthenticated(False)
      has_response = 1
    elif domain.domain_type == self.SUB:
      if self.checkAlert(remote_xml) or \
          (xml_confirmation, syncml_data) != ('', ''):
        subscriber.setLastSentMessage(xml_a)
        self.sendResponse(
                  from_url=domain.subscription_url,
                  to_url=domain.publication_url,
                  sync_id=domain.getTitle(),
                  xml=xml_a, domain=domain,
                  content_type=domain.getSyncContentType())
        has_response = 1
      else:
        if domain.isOneWayFromServer():
          self.deleteRemainObjectList(domain, subscriber)
        LOG('this is the end of the synchronisation session !!!', INFO, domain.getId())
        domain.setAuthenticated(False)
    return {'has_response':has_response, 'xml':xml_a}

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

  def wbxml2xml(self, wbxml):
    """
    convert wbxml string to xml using a temporary file
    """
    #LOG('wbxml2xml starting ...', DEBUG, '')
    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

  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 is None:
        subscriber = Subscriber(publication.generateNewId(), subscription_url)
        subscriber.setXMLMapping(publication.getXMLMapping())
        subscriber.setConduit(publication.getConduit())
        publication.addSubscriber(subscriber)
        subscriber = subscriber.__of__(publication)
        # first synchronization
        result = self.PubSyncInit(publication=publication,
                                  xml_client=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 is None and (subscription.getSubscriptionUrl()).find('file')>=0:
      msg = self.readResponse(sync_id=subscription.getSubscriptionUrl(),
          from_url=subscription.getSubscriptionUrl())
    if msg is 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 'SQLQueue'
    return 'RAMQueue'