Commit 291fafc5 authored by Aurel's avatar Aurel

New ERP5 SyncML implementation

Complete rewrite of the syncml core engine to make it scallable
Remaining work :
- review of conflict management
- re-implement splitting of big object
- generic unit test for AsynchronousEngine
- complete review of default conduit
- lot of TODO and XXX in the code to check
parent c7f46648
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>update_url</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>8.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Update URL to current site</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/SyncMLDocument_updateURLToCurrentSite</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>last_point_fixe_report</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>5.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Last Point Fixe Report</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/SyncMLSubscription_viewReport</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>update_url</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>8.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Update URL to current site</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/SyncMLDocument_updateURLToCurrentSite</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view_signatures</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>8.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Signatures</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/SyncMLSubscription_viewSignatureList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>point_fixe</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Check Point Fixe</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/SynchronizationTool_launchActivateCheckPointFixe</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>syncml</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>3.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>SyncML</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/SystemPreference_viewSyncML</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
##############################################################################
#
# Copyright (c) 2002-2012 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from lxml import etree
from difflib import unified_diff
from Products.ERP5Type.DiffUtils import DiffFile
def diffXML(xml_plugin="", xml_erp5="", html=True):
if isinstance(xml_erp5, unicode):
xml_erp5 = xml_erp5.encode('utf-8')
if xml_plugin == "":
xml_plugin="<object>Not found</object>"
if xml_erp5 == "":
xml_erp5="<object>Not found</object>"
xml = etree.fromstring(xml_erp5)
xml_erp5 = etree.tostring(xml, pretty_print=True, encoding="utf-8")
if isinstance(xml_plugin, unicode):
xml_plugin = xml_plugin.encode('utf-8')
xml = etree.fromstring(xml_plugin)
xml_plugin = etree.tostring(xml, pretty_print=True, encoding="utf-8")
diff_list = list(unified_diff(xml_plugin.split('\n'), xml_erp5.split('\n'), tofile="erp5 xml", fromfile="plugin xml", lineterm=''))
if len(diff_list) != 0:
diff_msg = '\n\nXML Diff :\n'
diff_msg += '\n'.join(diff_list)
if html:
return DiffFile(diff_msg).toHTML()
return diff_msg
else:
return 'No diff'
......@@ -63,9 +63,7 @@
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
<value> <string>Clients sends all its database to the server that is expected to replace all its entries</string> </value>
</item>
<item>
<key> <string>effective_date</string> </key>
......@@ -75,7 +73,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>refresh_from_content</string> </value>
<value> <string>refresh_from_client_only</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
......@@ -83,11 +81,11 @@
</item>
<item>
<key> <string>short_title</string> </key>
<value> <string>Refresh From Client</string> </value>
<value> <string>Refresh From Client Only</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Refresh From Client</string> </value>
<value> <string>Refresh From Client Only</string> </value>
</item>
</dictionary>
</pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Category" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_folders_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Copy_or_Move_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Delete_objects_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>codification</string> </key>
<value> <string>213</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>213</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>effective_date</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>chunk_accepted</string> </value>
</item>
<item>
<key> <string>int_index</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Category</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value> <string>Chunk Accepted</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Chunked item accepted and buffered</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Category" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_folders_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Copy_or_Move_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Delete_objects_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>codification</string> </key>
<value> <string>500</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>500</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>effective_date</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>command_failed</string> </value>
</item>
<item>
<key> <string>int_index</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Category</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value> <string>Failed</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Command Failed</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -6,6 +6,25 @@
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_property_domain_dict</string> </key>
<value>
<dictionary>
<item>
<key> <string>short_title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>acquire_local_roles</string> </key>
<value> <int>1</int> </value>
......@@ -63,4 +82,42 @@
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>short_title</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>title</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<workflow_chain>
<chain>
<type>SyncML Publication</type>
<workflow>edit_workflow, syncml_subcription_validation_workflow, syncml_subscription_authentication_workflow</workflow>
<workflow>edit_workflow, syncml_subcription_validation_workflow, syncml_subscription_authentication_workflow, syncml_synchronization_workflow</workflow>
</chain>
<chain>
<type>SyncML Signature</type>
......@@ -9,7 +9,7 @@
</chain>
<chain>
<type>SyncML Subscription</type>
<workflow>edit_workflow, syncml_subcription_validation_workflow, syncml_subscription_authentication_workflow</workflow>
<workflow>edit_workflow, syncml_subcription_validation_workflow, syncml_subscription_authentication_workflow, syncml_synchronization_workflow</workflow>
</chain>
<chain>
<type>Synchronization Tool</type>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Property Sheet" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>SyncML Preferences</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SyncMLPreference</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/int</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Number of documents to retrieved in one activity when getting documents modifications</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>preferred_document_retrieved_per_activity_count_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>preference</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: int(30)</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/int</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Number of activity to generate when getting documents modification</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>preferred_retrieval_activity_count_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>preference</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: int(100)</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/int</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Number of sync actions to process in one activity. If zero, no splitting is done</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>preferred_sync_action_per_activity_count_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>preference</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: int(0)</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_local_properties</string> </key>
<value>
<tuple>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>mode</string> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>string</string> </value>
</item>
</dictionary>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/string</string>
</tuple>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>authentication_format</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>The authenticated user who run the sync processs</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>authenticated_user_property</string> </value>
</item>
<item>
<key> <string>mode</string> </key>
<value> <string>w</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -27,7 +27,7 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/date</string>
<string>elementary_type/string</string>
</tuple>
</value>
</item>
......@@ -51,6 +51,10 @@
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: \'00000000T000000Z\'</string> </value>
</item>
</dictionary>
</pickle>
</record>
......
......@@ -27,7 +27,7 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/date</string>
<string>elementary_type/string</string>
</tuple>
</value>
</item>
......@@ -51,6 +51,10 @@
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: \'00000000T000000Z\'</string> </value>
</item>
</dictionary>
</pickle>
</record>
......
......@@ -85,7 +85,7 @@
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Subscription Url</string> </value>
<value> <string>Source Url</string> </value>
</item>
</dictionary>
</value>
......
......@@ -9,7 +9,9 @@
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
......@@ -81,6 +83,10 @@
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Validation State</string> </value>
</item>
</dictionary>
</value>
</item>
......
......@@ -85,7 +85,7 @@
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Publication Url</string> </value>
<value> <string>Target Url</string> </value>
</item>
</dictionary>
</value>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string># This script will update the url of pub/sub on which it is called to \n
# the current site URL\n
url = context.getPortalObject().absolute_url()\n
if context.getPortalType() == "SyncML Subscription":\n
context.edit(url_string=url,subscription_url_string=url)\n
else:\n
context.edit(url_string=url)\n
\n
message = context.Base_translateString(\'URL updated\')\n
return context.Base_redirect(form_id, keep_items={\'portal_status_message\' : message}, **kw)\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>form_id=None, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SyncMLDocument_updateURLToCurrentSite</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string encoding="cdata"><![CDATA[
"""\n
Returns the list of results of the specified process\n
or of the last process if nothing specified.\n
"""\n
\n
def getLastActiveProcess(sub):\n
"""\n
This returns the last active process finished. So it will\n
not returns the current one\n
"""\n
limit = 1\n
active_process_list = context.getPortalObject().portal_catalog(\n
portal_type=\'Active Process\', limit=limit,\n
sort_on=((\'creation_date\', \'DESC\'),\n
# XXX Work around poor resolution of MySQL dates.\n
(\'CONVERT(`catalog`.`id`, UNSIGNED)\', \'DESC\')),\n
causality_uid=sub.getUid())\n
if len(active_process_list) < limit:\n
process = None\n
else:\n
process = active_process_list[-1].getObject()\n
return process\n
\n
\n
if active_process is None:\n
active_process = getLastActiveProcess(context)\n
else:\n
active_process = context.getPortalObject().restrictedTraverse(active_process)\n
\n
result_list = []\n
\n
if active_process is not None:\n
result_list = [x for x in active_process.getResultList()]\n
# High severity will be displayed first\n
result_list.sort(key=lambda x: -x.severity)\n
\n
return result_list\n
]]></string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>active_process=None, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SyncMLSubscription_getReportResultList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string># Do not care about args so that objectValue is fast\n
return context.objectValues()\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>*args, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SyncMLSubscription_getSignatureList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -53,7 +53,7 @@
<value> <string>from Products.CMFCore.utils import getToolByName\n
portal = context.getPortalObject()\n
synchronization_tool = getToolByName(portal, \'portal_synchronizations\')\n
synchronization_tool.SubSync(context.getPath())\n
synchronization_tool.processClientSynchronization(context.getPath())\n
\n
message = context.Base_translateString(\'Synchronization started\')\n
return context.Base_redirect(form_id, keep_items={\'portal_status_message\' : message}, **kw)\n
......
......@@ -74,9 +74,7 @@
<item>
<key> <string>bottom</string> </key>
<value>
<list>
<string>listbox</string>
</list>
<list/>
</value>
</item>
<item>
......@@ -125,6 +123,7 @@
<string>my_next_anchor</string>
<string>my_translated_portal_type</string>
<string>my_translated_validation_state_title</string>
<string>my_synchronization_state_title</string>
</list>
</value>
</item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_synchronization_state_title</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_translated_validation_state_title</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewSYNCMLFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Synchronization State</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5Form" module="Products.ERP5Form.Form"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list>
<string>listbox</string>
</list>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list>
<string>listbox_detail</string>
</list>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SyncMLSubscription_viewReport</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>SyncMLSubscription_viewReport</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_view</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Report</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ListBox" module="Products.ERP5Form.ListBox"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>listbox</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>all_columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>count_method</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>domain_root_list</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>domain_tree</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable_columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>global_attributes</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>list_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>list_method</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>meta_types</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>page_template</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>portal_types</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>report_root_list</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>report_tree</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>search</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>search_columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>select</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>selection_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>sort</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>sort_columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>stat_columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>stat_method</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>url_columns</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>all_columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>count_method</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>domain_root_list</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>domain_tree</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable_columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>global_attributes</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>list_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>list_method</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>meta_types</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>page_template</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>portal_types</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>report_root_list</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>report_tree</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>search</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>search_columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>select</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>selection_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>sort</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>sort_columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>stat_columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>stat_method</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>url_columns</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>all_columns</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>anchor</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>columns</string> </key>
<value>
<list>
<tuple>
<string>summary</string>
<string>Summary</string>
</tuple>
<tuple>
<string>severity</string>
<string>Severity</string>
</tuple>
<tuple>
<string>detail</string>
<string>Details</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>count_method</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default_display_style</string> </key>
<value> <string>table</string> </value>
</item>
<item>
<key> <string>default_params</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_style_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>domain_root_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>domain_tree</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>editable_columns</string> </key>
<value>
<list>
<tuple>
<string>detail</string>
<string>Details</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>global_attributes</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>global_search_column</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>hide_rows_on_no_search_criterion</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>lines</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>list_action</string> </key>
<value> <string>list</string> </value>
</item>
<item>
<key> <string>list_method</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>meta_types</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>page_navigation_template</string> </key>
<value> <string>ListBox_viewSliderPageNavigationRenderer</string> </value>
</item>
<item>
<key> <string>page_template</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>portal_types</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>report_root_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>report_tree</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>row_css_method</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>search</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>search_columns</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>select</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>selection_name</string> </key>
<value> <string>syncml_subscription_view_latest_result_selection</string> </value>
</item>
<item>
<key> <string>sort</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>sort_columns</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>stat_columns</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>stat_method</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>style_columns</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Results</string> </value>
</item>
<item>
<key> <string>untranslatable_columns</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>url_columns</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Method" module="Products.Formulator.MethodField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>method_name</string> </key>
<value> <string>SyncMLSubscription_getReportResultList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TextAreaField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_detail</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>line_too_long</string> </key>
<value> <string>A line was too long.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>too_long</string> </key>
<value> <string>You entered too many characters.</string> </value>
</item>
<item>
<key> <string>too_many_lines</string> </key>
<value> <string>You entered too many lines.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <int>5</int> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Details</string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <int>80</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5Form" module="Products.ERP5Form.Form"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list>
<string>listbox</string>
</list>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SyncMLSubscription_viewSignatureList</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>SyncMLSubscription_viewSignatureList</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Signatures</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>columns</string>
<string>list_method</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>columns</string> </key>
<value>
<list>
<tuple>
<string>id</string>
<string>GID</string>
</tuple>
<tuple>
<string>title</string>
<string>Title</string>
</tuple>
<tuple>
<string>validation_state</string>
<string>validation_state</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_listbox</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>list_method</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Signatures</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Method" module="Products.Formulator.MethodField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>method_name</string> </key>
<value> <string>SyncMLSubscription_getSignatureList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string># This is the default "List Method ID" to use to list object from a module in erp5\n
# this method is optimized to return group of document based on what the syncml engine\n
# required\n
# XXX Some parameter are not managed (context_document, gid, etc)\n
\n
\n
if len(kw):\n
context.log("kw %s" %(kw,))\n
\n
catalog_kw = {\'limit\' : limit}\n
if min_id and id_list:\n
raise NotImplementError\n
\n
if min_id:\n
catalog_kw[\'id\'] = {\'query\': min_id, \'range\': \'nlt\'}\n
elif id_list:\n
catalog_kw[\'id\'] = {\'query\': id_list, \'operator\': \'in\'}\n
\n
\n
return context.searchFolder(sort_on=((\'id\',\'ascending\'),), **catalog_kw)\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>id_only=False, min_id=None, id_list=None, limit=None, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SyncML_searchFolder</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string encoding="cdata"><![CDATA[
sub_path = method_kw.get("subscription_path")\n
sub = context.getPortalObject().restrictedTraverse(sub_path)\n
search_kw = dict(kw)\n
packet_size = search_kw.pop(\'packet_size\', 30)\n
limit = packet_size * search_kw.pop(\'activity_count\', 100)\n
\n
r = sub.getDocumentIdList(limit=limit, **search_kw)\n
\n
result_count = len(r)\n
if result_count:\n
if result_count == limit:\n
# Recursive call to prevent too many activity generation\n
next_kw = dict(activate_kw, priority=1+activate_kw.get(\'priority\', 1))\n
kw["min_id"] = r[-1].getId()\n
sub.activate(**next_kw).SynchronizationTool_activateCheckPointFixe(\n
callback, method_kw, activate_kw, **kw)\n
\n
r = [x.getId() for x in r]\n
callback_method = getattr(sub.activate(**activate_kw), callback)\n
for i in xrange(0, result_count, packet_size):\n
callback_method(id_list=r[i:i+packet_size],\n
**method_kw)\n
\n
if result_count < limit:\n
# Register end of point fixe\n
from Products.CMFActivity.ActiveResult import ActiveResult\n
active_result = ActiveResult()\n
active_result.edit(summary=\'Info\',\n
severity=0,\n
detail="Point fixe check ended at %r" % (DateTime().strftime("%d/%m/%Y %H:%M"),))\n
sub.activate(active_process=method_kw["active_process"],\n
activity=\'SQLQueue\', \n
priority=2,).ERP5Site_saveCheckCatalogTableResult(active_result)\n
]]></string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>callback, method_kw, activate_kw, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SynchronizationTool_activateCheckPointFixe</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>portal = context.getPortalObject()\n
publication = portal.restrictedTraverse(publication_path)\n
subscription = portal.restrictedTraverse(subscription_path)\n
\n
\n
# First we must get list of gid from subscriptions objects\n
sub_xml_method_id = subscription.getXmlBindingGeneratorMethodId()\n
pub_xml_method_id = publication.getXmlBindingGeneratorMethodId()\n
\n
diff_list = []\n
append = diff_list.append\n
\n
# Browse objects from subscription\n
sub_object_list = list(subscription.getDocumentList(id_list=id_list))\n
for sub_object in sub_object_list:\n
# Get their gid\n
gid = subscription.getGidFromObject(sub_object)\n
# Retrieve the corresponding document from the publication\n
pub_object = publication.getDocumentFromGid(gid)\n
# Compare their xml\n
try:\n
sub_xml = getattr(sub_object, sub_xml_method_id)(context_document=subscription)\n
except TypeError:\n
sub_xml = getattr(sub_object, sub_xml_method_id)()\n
if pub_object:\n
try:\n
pub_xml = getattr(pub_object, pub_xml_method_id)(context_document=publication)\n
except TypeError:\n
pub_xml = getattr(pub_object, pub_xml_method_id)()\n
else:\n
pub_xml = ""\n
diff = portal.diffXML(xml_plugin=sub_xml, xml_erp5=pub_xml, html=False)\n
context.log("Got diff for GID %s" %(gid,))\n
if diff != "No diff":\n
append(diff)\n
\n
if len(diff_list):\n
severity = len(diff_list)\n
from Products.CMFActivity.ActiveResult import ActiveResult\n
active_result = ActiveResult()\n
active_result.edit(summary=\'Failed\',\n
severity=severity,\n
detail=\'\\n\'.join(diff_list))\n
subscription.activate(active_process=active_process,\n
activity=\'SQLQueue\', \n
priority=2,).ERP5Site_saveCheckCatalogTableResult(active_result)\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>publication_path, subscription_path, id_list, active_process</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SynchronizationTool_checkPointFixe</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>request = context.REQUEST\n
site = context.getPortalObject()\n
Base_translateString = site.Base_translateString\n
error_message = None\n
uids = site.portal_selections.getSelectionCheckedUidsFor(selection_name)\n
if len(uids) != 2:\n
error_message = Base_translateString("Please select one publication and one subscription.")\n
\n
object_list = [x.getObject() for x in site.portal_catalog(uid=uids)]\n
\n
pub = None\n
sub = None\n
r1 = object_list[0]\n
if r1.getPortalType() == "SyncML Publication":\n
pub = r1\n
else:\n
sub = r1\n
\n
r2 = object_list[1]\n
if r2.getPortalType() == "SyncML Publication":\n
pub = r2\n
else:\n
sub = r2\n
\n
if not pub or not sub:\n
error_message = Base_translateString("Please select one publication and one subscription.")\n
\n
if error_message:\n
qs = \'?portal_status_message=%s\' % error_message\n
return request.RESPONSE.redirect( context.absolute_url() + \'/\' + form_id + qs )\n
\n
\n
from DateTime import DateTime\n
\n
callback = "SynchronizationTool_checkPointFixe"\n
active_process_path = site.portal_activities.newActiveProcess(start_date=DateTime(), causality_value=sub).getPath()\n
method_kw = {\n
"publication_path" : pub.getPath(),\n
"subscription_path" : sub.getPath(),\n
"active_process" : active_process_path,\n
}\n
activate_kw = {\n
"priority" : 3,\n
"activity" : "SQLQueue",\n
}\n
\n
# Register start of point fixe\n
from Products.CMFActivity.ActiveResult import ActiveResult\n
active_result = ActiveResult()\n
active_result.edit(summary=\'Info\',\n
severity=0,\n
detail="Point fixe check launched at %r" % (DateTime().strftime("%d/%m/%Y %H:%M"),))\n
sub.activate(active_process=active_process_path,\n
activity=\'SQLQueue\',\n
priority=2,).ERP5Site_saveCheckCatalogTableResult(active_result)\n
\n
context.SynchronizationTool_activateCheckPointFixe(callback=callback, method_kw=method_kw, activate_kw=activate_kw)\n
\n
qs = \'?portal_status_message=%s\' % "Point fixe running, active process path is %s" % (active_process_path,)\n
return request.RESPONSE.redirect( context.absolute_url() + \'/\' + form_id + qs )\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>selection_name, form_id</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SynchronizationTool_launchActivateCheckPointFixe</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5Form" module="Products.ERP5Form.Form"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>your_diff</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SynchronizationTool_viewPointFixe</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>SynchronizationTool_viewPointFixe</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_dialog</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Point Fixe</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="EditorField" module="Products.ERP5Form.EditorField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>your_diff</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>line_too_long</string> </key>
<value> <string>A line was too long.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>too_long</string> </key>
<value> <string>You entered too many characters.</string> </value>
</item>
<item>
<key> <string>too_many_lines</string> </key>
<value> <string>You entered too many lines.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>text_editor</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>text_editor</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <int>5</int> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>text_editor</string> </key>
<value> <string>text_area</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>XML Diff</string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <int>40</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>here/SynchronizationTool_checkPointFixe</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -112,16 +112,7 @@
<item>
<key> <string>portal_types</string> </key>
<value>
<list>
<tuple>
<string>SyncML Subscription</string>
<string>SyncML Subscription</string>
</tuple>
<tuple>
<string>SyncML Publication</string>
<string>SyncML Publication</string>
</tuple>
</list>
<list/>
</value>
</item>
<item>
......
......@@ -2,122 +2,138 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Base Type" module="erp5.portal_type"/>
<global name="ERP5Form" module="Products.ERP5Form.Form"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_property_domain_dict</string> </key>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>short_title</string> </key>
<key> <string>_asgns</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<dictionary/>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>acquire_local_roles</string> </key>
<value> <int>0</int> </value>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>content_icon</string> </key>
<value> <string>folder_icon.gif</string> </value>
<key> <string>action</string> </key>
<value> <string>Base_edit</string> </value>
</item>
<item>
<key> <string>content_meta_type</string> </key>
<value> <string>ERP5 Synchronizations</string> </value>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>ERP5 default document. Supports synchronisation and XML.</string> </value>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>factory</string> </key>
<value> <string>addFolder</string> </value>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>filter_content_types</string> </key>
<value> <int>1</int> </value>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<tuple/>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Synchronization Tool</string> </value>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>init_script</string> </key>
<key> <string>left</string> </key>
<value>
<none/>
<list>
<string>my_preferred_retrieval_activity_count</string>
<string>my_preferred_document_retrieved_per_activity_count</string>
</list>
</value>
</item>
<item>
<key> <string>permission</string> </key>
<key> <string>right</string> </key>
<value>
<none/>
<list>
<string>my_preferred_sync_action_per_activity_count</string>
</list>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
<key> <string>id</string> </key>
<value> <string>SystemPreference_viewSyncML</string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>SynchronizationTool</string> </value>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>type_mixin</string> </key>
<value>
<tuple/>
</value>
<key> <string>name</string> </key>
<value> <string>SystemPreference_viewSyncML</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value> <string>erp5_ui</string> </value>
<key> <string>pt</string> </key>
<value> <string>form_view</string> </value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>short_title</string> </value>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>SyncML</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value> <string>erp5_ui</string> </value>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>title</string> </value>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>display_width</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_preferred_document_retrieved_per_activity_count</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_integer_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Number of retrieved documents per activity</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>display_width</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_preferred_retrieval_activity_count</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_integer_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Number of activities retrieving documents</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>display_width</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_preferred_sync_action_per_activity_count</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_integer_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Number of actions to perform per activity</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>diffXML</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>SyncMLTool</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>diffXML</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -51,8 +51,10 @@
<item>
<key> <string>_body</string> </key>
<value> <string>signature = state_change[\'object\']\n
signature.edit(temporary_data=None,\n
partial_data=None)\n
if signature.hasPartialData():\n
signature.setPartialData(None)\n
if signature.hasTemporaryData():\n
signature.setTemporaryData(None)\n
</string> </value>
</item>
<item>
......
......@@ -52,18 +52,27 @@
<key> <string>_body</string> </key>
<value> <string>signature = state_change[\'object\']\n
\n
edit_kw = {}\n
\n
temporary_data = signature.getTemporaryData()\n
if temporary_data is not None:\n
# This happens when we have sent the xml\n
# and we just get the confirmation\n
signature.setData(temporary_data)\n
signature.edit(force=False,\n
temporary_data=None,\n
partial_data=None,\n
subscriber_xupdate=None,\n
publisher_xupdate=None)\n
edit_kw["temporary_data"] = None\n
\n
if signature.isForce():\n
edit_kw["force"] = False\n
if signature.hasPartialData():\n
edit_kw["partial_data"] = None\n
if signature.hasSubscriberXupdate():\n
edit_kw["subscriber_xupdate"] = None\n
if signature.hasPublisherXupdate():\n
edit_kw["publisher_xupdate"] = None\n
\n
if len(edit_kw):\n
signature.edit(**edit_kw)\n
\n
signature.getParentValue().removeRemainingObjectPath(signature.getReference())\n
context.SyncMLSignature_resetConflictList(state_change)\n
</string> </value>
</item>
......
......@@ -23,6 +23,7 @@
<value>
<tuple>
<string>change_to_partial</string>
<string>do_sync</string>
<string>synchronize</string>
</tuple>
</value>
......
......@@ -23,6 +23,7 @@
<value>
<tuple>
<string>change_to_partial</string>
<string>do_sync</string>
<string>synchronize</string>
</tuple>
</value>
......
......@@ -24,6 +24,7 @@
<tuple>
<string>change_to_conflict</string>
<string>change_to_partial</string>
<string>do_sync</string>
<string>synchronize</string>
</tuple>
</value>
......
......@@ -23,6 +23,7 @@
<value>
<tuple>
<string>change_to_conflict</string>
<string>do_sync</string>
<string>synchronize</string>
</tuple>
</value>
......
......@@ -24,7 +24,6 @@
<tuple>
<string>change_to_conflict</string>
<string>drift</string>
<string>synchronize</string>
</tuple>
</value>
</item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>syncing</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>change_to_conflict</string>
<string>synchronize</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>do_sync</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>syncing</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="DCWorkflowDefinition" module="Products.DCWorkflow.DCWorkflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>creation_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Defines the state of a syncml synchronization</string> </value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>syncml_synchronization_workflow</string> </value>
</item>
<item>
<key> <string>initial_state</string> </key>
<value> <string>not_running</string> </value>
</item>
<item>
<key> <string>manager_bypass</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>Access contents information</string>
<string>View</string>
<string>Add portal content</string>
<string>Modify portal content</string>
<string>Delete objects</string>
</tuple>
</value>
</item>
<item>
<key> <string>state_var</string> </key>
<value> <string>synchronization_state</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>SyncML Synchronization Worfklow</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Scripts" module="Products.DCWorkflow.Scripts"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>scripts</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="States" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>states</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>The synchronization process is finished</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>finished</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Finished</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>initialize</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>SyncML Client or Server is in initialization state exchanging about : \r\n
- Credentials approval\r\n
- database sync type wanted</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>initializing</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Initializing</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>finish</string>
<string>finish_action</string>
<string>process_sync_request</string>
<string>send_modifications</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>Initial state of a synchronization process</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>not_running</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Not Running</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>initialize</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>The SyncML client or server is processing the received requests </string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>processing_sync_requests</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Processing Sync Request</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>finish</string>
<string>finish_action</string>
<string>send_modifications</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>The syncml client or server is sending all data modifications which have happened since the previous sync</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>sending_modifications</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Sending Modifications</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>finish</string>
<string>finish_action</string>
<string>process_sync_request</string>
<string>wait_notifications</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>waiting_notifications</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>finish</string>
<string>finish_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Transitions" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>transitions</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Mark the data synchronization process as finished</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>finish</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>finished</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Finish</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string>Finish Synchronization Manually</string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string>%(content_url)s/Base_viewWorkflowActionDialog?workflow_action=finish_action</string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string>finish</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>This transition is usefull for debugging purpose. It should be actionned with caution and should not be available to any user</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>finish_action</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Finish Synchronization Manually</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Guard" module="Products.DCWorkflow.Guard"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Start the initialization phase of a data synchronization process</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>initialize</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>initializing</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Initialize</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Client or server will process sync request received</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>process_sync_request</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>processing_sync_requests</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Process sync request</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Client or server will send its data modification</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>send_modifications</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>sending_modifications</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Send modifications</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>wait_notifications</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>waiting_notifications</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Wait for notifications</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Variables" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variables</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Transition id</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>action</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>transition/getId|nothing</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Name of the user who performed transition</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>actor</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Comment about transition</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>comment</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python:state_change.kwargs.get(\'comment\', \'\')</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Error message if validation failed</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>error_message</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Provides access to workflow history</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>history</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>state_change/getHistory</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Portal type (used as filter for worklists)</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>portal_type</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Transition timestamp</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>time</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>state_change/getDateTime</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Worklists" module="Products.DCWorkflow.Worklists"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>worklists</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
nicolas
\ No newline at end of file
aurel
\ No newline at end of file
72
\ No newline at end of file
89
\ No newline at end of file
SyncML Publication | reset_publication
SyncML Publication | update_url
SyncML Publication | view
SyncML Publication | view_conflict
SyncML Signature | view
SyncML Subscription | last_point_fixe_report
SyncML Subscription | reset_subscription
SyncML Subscription | start_synchronization
SyncML Subscription | update_url
SyncML Subscription | vew
SyncML Subscription | view_conflict
SyncML Subscription | view_signatures
Synchronization Tool | point_fixe
Synchronization Tool | view
System Preference | syncml
portal_actions | portal_synchronizations_action
\ No newline at end of file
SyncMLTool
\ No newline at end of file
SyncML Publication | edit_workflow
SyncML Publication | syncml_subcription_validation_workflow
SyncML Publication | syncml_subscription_authentication_workflow
SyncML Publication | syncml_synchronization_workflow
SyncML Signature | edit_workflow
SyncML Signature | syncml_signature_validation_workflow
SyncML Subscription | edit_workflow
SyncML Subscription | syncml_subcription_validation_workflow
SyncML Subscription | syncml_subscription_authentication_workflow
SyncML Subscription | syncml_synchronization_workflow
Synchronization Tool | edit_workflow
\ No newline at end of file
......@@ -4,3 +4,4 @@ SyncMLPublication
SyncMLSubscriptionConstraint
SyncMLSubscription
Gpg
SyncMLPreference
\ No newline at end of file
syncml_signature_validation_workflow
syncml_subcription_validation_workflow
syncml_subscription_authentication_workflow
syncml_synchronization_workflow
\ No newline at end of file
<module>
<id>syncml_test_person_client_module</id>
<permission_list>
</permission_list>
<portal_type>Person Module</portal_type>
<title>syncml_test_person_client_module</title>
</module>
\ No newline at end of file
<module>
<id>syncml_test_person_server_module</id>
<permission_list>
</permission_list>
<portal_type>Person Module</portal_type>
<title>syncml_test_person_server_module</title>
</module>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SyncML Publication" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Delete_objects_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_local_properties</string> </key>
<value>
<tuple>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>modification_date</string> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>date</string> </value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>creation_date</string> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>date</string> </value>
</item>
</dictionary>
</tuple>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>source/syncml_test_person_server_module</string>
</tuple>
</value>
</item>
<item>
<key> <string>conduit_module_id</string> </key>
<value> <string>ERP5Conduit</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/vnd.syncml+xml</string> </value>
</item>
<item>
<key> <string>creation_date</string> </key>
<value>
<object>
<klass>
<global id="1.1" name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1325804400.0</float>
<string>GMT+1</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>test_person_pub</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_person_pub</string> </value>
</item>
<item>
<key> <string>is_activity_enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>list_method_id</string> </key>
<value> <string>SyncML_searchFolder</string> </value>
</item>
<item>
<key> <string>modification_date</string> </key>
<value>
<object>
<klass> <reference id="1.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1325804400.0</float>
<string>GMT+1</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>SyncML Publication</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>SyncML Test Publication</string> </value>
</item>
<item>
<key> <string>url_string</string> </key>
<value> <string>http://11.0.149.161:12001/erp5</string> </value>
</item>
<item>
<key> <string>xml_binding_generator_method_id</string> </key>
<value> <string>asXML</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SyncML Subscription" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_local_properties</string> </key>
<value>
<tuple>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>syncml_alert_mode</string> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>string</string> </value>
</item>
</dictionary>
</tuple>
</value>
</item>
<item>
<key> <string>authenticated_user</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>syncml_alert_code/refresh_from_client_only</string>
<string>source/syncml_test_person_client_module</string>
</tuple>
</value>
</item>
<item>
<key> <string>conduit_module_id</string> </key>
<value> <string>ERP5Conduit</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/vnd.syncml+xml</string> </value>
</item>
<item>
<key> <string>default_destination_reference</string> </key>
<value> <string>test_person_pub</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>test_person_sub</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_person_sub</string> </value>
</item>
<item>
<key> <string>is_activity_enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>last_anchor</string> </key>
<value> <string>00000000T000000Z</string> </value>
</item>
<item>
<key> <string>list_method_id</string> </key>
<value> <string>SyncML_searchFolder</string> </value>
</item>
<item>
<key> <string>next_anchor</string> </key>
<value> <string>20120112T071139Z</string> </value>
</item>
<item>
<key> <string>password</string> </key>
<value> <string>syncml</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>SyncML Subscription</string> </value>
</item>
<item>
<key> <string>session_id</string> </key>
<value> <int>24</int> </value>
</item>
<item>
<key> <string>subscription_url_string</string> </key>
<value> <string>http://11.0.149.161:12001/erp5</string> </value>
</item>
<item>
<key> <string>syncml_alert_mode</string> </key>
<value> <string>refresh_from_client_only</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>SyncML Test Subscription</string> </value>
</item>
<item>
<key> <string>url_string</string> </key>
<value> <string>http://11.0.149.161:12001/erp5</string> </value>
</item>
<item>
<key> <string>user_id</string> </key>
<value> <string>syncml</string> </value>
</item>
<item>
<key> <string>xml_binding_generator_method_id</string> </key>
<value> <string>asXML</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
##############################################################################
#
# Copyright (c) 2002-2012 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from Products.ERP5SyncML.tests.testERP5SyncMLMixin import TestERP5SyncMLMixin
class testSyncMLAsynchronousEngine(TestERP5SyncMLMixin):
"""
Test SyncML in Asynchronous mode
"""
def getTitle(self):
return "Test SyncML with asynchronous engine"
def getBusinessTemplateList(self):
"""
Tuple of Business Templates we need to install
"""
return ('erp5_base', 'erp5_syncml', 'erp5_syncml_test_data') # bt5 for test data
def afterSetUp(self):
"""
This is ran before anything, used to set the environment
"""
self.sync_tool = self.portal.portal_synchronizations
# here, you can create the categories and objects your test will depend on
def _initSyncModule(self):
"""
Init and clear modules used in sync test
"""
self.client_module = self.portal.get('syncml_test_person_client_module', None)
self.server_module = self.portal.get('syncml_test_person_server_module', None)
self.assertNotEqual(self.client_module, None)
self.assertNotEqual(self.server_module, None)
self.client_module.manage_delObjects(ids=list(self.client_module.objectIds()))
self.server_module.manage_delObjects(ids=list(self.server_module.objectIds()))
self.assertEqual(len(self.client_module), 0)
self.assertEqual(len(self.server_module), 0)
def _initSynchronization(self):
""" Init pubs & subs """
self.pub = self.sync_tool.get("test_person_pub")
self.sub = self.sync_tool.get("test_person_sub")
self.assertNotEqual(self.pub, None)
self.assertNotEqual(self.sub, None)
if not self.pub.getValidationState() == "validated":
self.pub.validate()
if not self.sub.getValidationState() == "validated":
self.sub.validate()
# Reset from previous sync
self.sub.SyncMLSubscription_resetSubscription()
self.pub.SyncMLPublication_resetPublication()
# Make sure of initial state of sub
if self.sub.getSynchronizationState() not in ("finished",
"not_running"):
self.sub.finish()
# Use url of the current site
self.updateSynchronizationURL(object_list = [self.pub, self.sub])
# Update authentication
user = password = "syncml"
self.addSynchronizationUser(user, password)
self.updateAuthenticationCredentials(user, password, [self.sub,])
def _updateSyncMLPreference(self, activity_count=100, doc_count=30,
sync_count=0):
pref = self.portal.portal_preferences.getActiveSystemPreference()
if not pref:
pref = self.portal.portal_preferences.newContent(
portal_type="System Preference")
pref.edit(preferred_document_retrieved_per_activity_count=doc_count,
preferred_retrieval_activity_count=activity_count,
preferred_sync_action_per_activity_count=sync_count)
if not pref.getPreferenceState() != "enabled":
pref.enable()
def _fillModule(self, module, nb_objects):
self.title_list = []
append = self.title_list.append
for x in xrange(nb_objects):
module.newContent(title=str(x))
append(str(x))
def _setSyncMode(self, mode):
self.sub.edit(syncml_alert_mode=mode)
def test_01(self, *args, **kw):
"""
test the synchronization without splitting of sync action in activities
We generate 3 activity of 5 documents and synchronizing 50 documents
so that getAndActivate will be call many times
"""
self._initSynchronization()
self._initSyncModule()
self.tic()
# Init the sync
self._updateSyncMLPreference(activity_count=3, doc_count=5) # Process 15 docs
nb_document=50
self._fillModule(module=self.client_module, nb_objects=nb_document)
self._setSyncMode("refresh_from_client_only")
self.tic()
# Initial check
self.assertEqual(len(self.client_module), nb_document)
self.assertEqual(len(self.server_module), 0)
# Do the sync
self.sync_tool.processClientSynchronization(self.sub.getRelativeUrl())
self.tic()
# Check result
self.assertEqual(self.sub.getSynchronizationState(), "finished")
self.assertEqual(len(self.client_module), nb_document)
self.assertEqual(len(self.server_module), nb_document)
self.assertEqual(len(self.title_list), 50)
for person in self.server_module.objectValues():
self.title_list.remove(person.getTitle())
self.assertEqual(len(self.title_list), 0)
def test_02_noSyncCommandSplitting(self, *args, **kw):
"""
test the synchronization without splitting of sync action in activities
We generate 35activity of 5 documents and synchronizing 50 documents
so that getAndActivate will be call two times only, and the last row
of document will have the length of the limit defined (25)
"""
self._initSynchronization()
self._initSyncModule()
self.tic()
# Init the sync
self._updateSyncMLPreference(activity_count=5, doc_count=5) # Process 25 docs
nb_document=50
self._fillModule(module=self.client_module, nb_objects=nb_document)
self._setSyncMode("refresh_from_client_only")
self.tic()
# Initial check
self.assertEqual(len(self.client_module), nb_document)
self.assertEqual(len(self.server_module), 0)
# Do the sync
self.sync_tool.processClientSynchronization(self.sub.getRelativeUrl())
self.tic()
# Check result
self.assertEqual(self.sub.getSynchronizationState(), "finished")
self.assertEqual(len(self.client_module), nb_document)
self.assertEqual(len(self.server_module), nb_document)
self.assertEqual(len(self.title_list), 50)
for person in self.server_module.objectValues():
self.title_list.remove(person.getTitle())
self.assertEqual(len(self.title_list), 0)
def test_03(self, *args, **kw):
"""
test the synchronization without splitting of sync action in activities
We generate 3 activity of 5 documents and synchronizing 12 documents
so that getAndActivate will be call one time and final activity must on 2
documents
Note that this test pass even if final activity contains more documents as
we are processing activity on one node only and so the same document creation
process can't be done in parralell and result in the same document created twice
This is has to be unit tested at the Subscription level with mock objects
"""
self._initSynchronization()
self._initSyncModule()
self.tic()
# Init the sync
self._updateSyncMLPreference(activity_count=3, doc_count=5) # Process 15 docs
nb_document=12
self._fillModule(module=self.client_module, nb_objects=nb_document)
self._setSyncMode("refresh_from_client_only")
self.tic()
# Initial check
self.assertEqual(len(self.client_module), nb_document)
self.assertEqual(len(self.server_module), 0)
# Do the sync
self.sync_tool.processClientSynchronization(self.sub.getRelativeUrl())
self.tic()
# Check result
self.assertEqual(self.sub.getSynchronizationState(), "finished")
self.assertEqual(len(self.client_module), nb_document)
self.assertEqual(len(self.server_module), nb_document)
self.assertEqual(len(self.title_list), nb_document)
for person in self.server_module.objectValues():
self.title_list.remove(person.getTitle())
self.assertEqual(len(self.title_list), 0)
def test_05_SyncMLSubscription(self):
"""
Test methods defined on subscription
"""
self._initSynchronization()
self._initSyncModule()
self.tic()
# Init the sync
nb_document=500
self._fillModule(module=self.client_module, nb_objects=nb_document)
self.tic()
# Check the default getDocumentIdList behaviour
r = self.sub.getDocumentIdList(limit=None)
self.assertEqual(len(r), nb_document)
# Now simulate the get and activate method
# Test limit is well taken into account
limit = 100
r = self.sub.getDocumentIdList(limit=limit)
self.assertEqual(len(r), limit)
# Test the min_id parameter
min_id = r[-1].getId()
r = self.sub.getDocumentIdList(limit=None, min_id=min_id)
self.assertEqual(len(r), nb_document-limit)
2012-01-09 Aurel :
- initial version
\ No newline at end of file
erp5_syncml
\ No newline at end of file
This bt5 contains test data for live unit tests of syncml
\ No newline at end of file
GPL
\ No newline at end of file
Aurel
\ No newline at end of file
3
\ No newline at end of file
syncml_test_person_client_module
syncml_test_person_server_module
\ No newline at end of file
portal_synchronizations/test_person_pub
portal_synchronizations/test_person_sub
\ No newline at end of file
testSyncMLAsynchronousEngine
\ No newline at end of file
erp5_syncml_test_data
\ No newline at end of file
0.1
\ No newline at end of file
......@@ -28,8 +28,8 @@
#
##############################################################################
from Products.ERP5SyncML.SyncMLConstant import XUPDATE_ELEMENT,\
XUPDATE_INSERT_OR_ADD_LIST, XUPDATE_DEL, XUPDATE_UPDATE
from Products.ERP5SyncML.SyncMLConstant import XUPDATE_INSERT_OR_ADD_LIST, \
XUPDATE_DEL, XUPDATE_UPDATE
from Products.ERP5Type.XMLExportImport import MARSHALLER_NAMESPACE_URI
from zLOG import LOG, INFO
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
......
......@@ -659,7 +659,7 @@ class Transaction(TioSafeBrain):
for erp5_sync in erp5_sync_list:
try:
resource = erp5_sync.getObjectFromGid(b16encode(resource_gid))
resource = erp5_sync.getDocumentFromGid(b16encode(resource_gid))
break
except (ValueError, AttributeError):
resource = None
......
......@@ -26,7 +26,6 @@
##############################################################################
from lxml import etree
from zLOG import LOG
from Products.ERP5SyncML.XMLSyncUtils import getConduitByName
from difflib import unified_diff
from Products.ERP5Type.DiffUtils import DiffFile
......@@ -43,6 +42,11 @@ def callAddNodeOnConduit(self, conduit_id, uid):
def diffXML(xml_plugin="", xml_erp5="", html=True):
if isinstance(xml_erp5, unicode):
xml_erp5 = xml_erp5.encode('utf-8')
if isinstance(xml_plugin, unicode):
xml_plugin = xml_plugin.encode('utf-8')
diff_list = list(unified_diff(xml_plugin.split('\n'), xml_erp5.split('\n'), tofile="erp5 xml", fromfile="plugin xml", lineterm=''))
if len(diff_list) != 0:
diff_msg = '\n\nTioSafe XML Diff :\n'
......
......@@ -85,7 +85,7 @@ for sync in sync_list:\n
after_method_id=[\'reset\', \'manage_delObjects\', \'unindexObject\',\n
\'sendHttpResponse\', \'PubSync\',\n
\'activateSyncModif\', \'immediateReindexObject\'],\n
after_tag=after_tag).SubSync(sub.getPath())\n
after_tag=after_tag).processClientSynchronization(sub.getPath())\n
afer_tag = [sub.getId(), pub.getId(), tag]\n
portal_status_message = translateString("Synchronization started.")\n
\n
......@@ -94,6 +94,8 @@ context.activate(after_tag=after_tag).getTitle()\n
\n
if not batch_mode:\n
context.Base_redirect(form_id, keep_items = dict(portal_status_message=portal_status_message))\n
else:\n
return sub\n
</string> </value>
</item>
<item>
......
631
\ No newline at end of file
634
\ No newline at end of file
......@@ -200,7 +200,7 @@ class MagentoTransaction(MagentoBrain):
for erp5_sync in erp5_sync_list:
try:
resource = erp5_sync.getObjectFromGid(b16encode(resource_gid))
resource = erp5_sync.getDocumentFromGid(b16encode(resource_gid))
break
except (ValueError, AttributeError):
resource = None
......
......@@ -99,7 +99,7 @@ class OscommerceERP5NodeConduit(TioSafeBaseConduit):
document.setCareerSubordinationValue(None)
else:
for synchronization in synchronization_list:
link_object = synchronization.getObjectFromGid(b16encode(organisation_gid))
link_object = synchronization.getDocumentFromGid(b16encode(organisation_gid))
if link_object is not None:
break
if link_object is not None:
......@@ -114,7 +114,7 @@ class OscommerceERP5NodeConduit(TioSafeBaseConduit):
""" This is the method calling to create an object. """
if DEBUG:
LOG("ERP5NodeContuide._createContent", INFO, "xml = %s" %(etree.tostring(xml, pretty_print=True),))
if object_id is not None:
if True: # object_id is not None:
sub_object = None
if sub_object is None: # If so, it doesn't exist
sub_object, reset_local_roles, reset_workflow = self.constructContent(
......
......@@ -59,8 +59,8 @@ class OscommerceERP5TransactionConduit(TioSafeBaseConduit):
"""
if object_id is None:
object_id = self.getAttribute(xml, 'id')
if object_id is not None:
if sub_object is None:
if True: # object_id is not None:
if sub_object is None and object_id:
try:
sub_object = object._getOb(object_id)
except (AttributeError, KeyError, TypeError):
......@@ -234,8 +234,8 @@ class OscommerceERP5TransactionConduit(TioSafeBaseConduit):
else:
for synchronization in synchronization_list:
# encode to the output type
if getattr(synchronization, 'getObjectFromGid', None) is not None:
link_object = synchronization.getObjectFromGid(b16encode(link_gid))
if getattr(synchronization, 'getDocumentFromGid', None) is not None:
link_object = synchronization.getDocumentFromGid(b16encode(link_gid))
#LOG("trying to get %s from %s, got %s" %(link_gid, synchronization.getPath(), link_object), 300, "This is for category type %s" %(category))
if link_object is not None:
break
......@@ -285,7 +285,7 @@ class OscommerceERP5TransactionConduit(TioSafeBaseConduit):
else:
synchronization_list = self.getSynchronizationObjectListForType(kw.get('domain'), 'Product', 'publication')
for synchronization in synchronization_list:
link_object = synchronization.getObjectFromGid(b16encode(link_gid))
link_object = synchronization.getDocumentFromGid(b16encode(link_gid))
if link_object is not None:
break
# in the worse case save the line with the unknown product
......
292
\ No newline at end of file
293
\ No newline at end of file
......@@ -31,7 +31,6 @@ import unittest
from zLOG import LOG
from Testing import ZopeTestCase
from AccessControl.SecurityManagement import newSecurityManager
from Products.ERP5Type.tests.backportUnittest import expectedFailure
class TestOxatisSynchronization(ERP5TypeTestCase):
......@@ -123,10 +122,11 @@ class TestOxatisSynchronization(ERP5TypeTestCase):
'person_module', 'delivered_person_module',]:
LOG("RUNNING SYNCHRO FOR %s" %(im), 300, "")
self.tic()
self.oxatis.IntegrationSite_synchronize(reset=reset, synchronization_list=[im,],
sub = self.oxatis.IntegrationSite_synchronize(reset=reset,
synchronization_list=[im,],
batch_mode=True)
self.tic()
self.assertEqual(sub.getSynchronizationState(), "finished")
if conflict_dict and conflict_dict.has_key(im):
nb_pub_conflict, nb_sub_conflict, in_conflict = conflict_dict[im]
self.checkConflicts(im, nb_pub_conflict, nb_sub_conflict, in_conflict)
......@@ -629,7 +629,6 @@ class TestOxatisSynchronization(ERP5TypeTestCase):
self.assertNotEqual(sale_order.getDestinationDecision(), self.oxatis.getDestination())
self.assertNotEqual(sale_order.getDestinationAdministration(), self.oxatis.getDestination())
@expectedFailure
def testFullSync(self):
self.runPersonSync()
......
73
\ No newline at end of file
74
\ No newline at end of file
......@@ -301,7 +301,7 @@ class Transaction(TransactionBrain):
for erp5_sync in erp5_sync_list:
try:
resource = erp5_sync.getObjectFromGid(b16encode(resource_gid))
resource = erp5_sync.getDocumentFromGid(b16encode(resource_gid))
break
except (ValueError, AttributeError):
resource = None
......
......@@ -59,8 +59,8 @@ class ERP5TransactionConduit(TioSafeBaseConduit):
"""
if object_id is None:
object_id = self.getAttribute(xml, 'id')
if object_id is not None:
if sub_object is None:
if True: # object_id is not None:
if sub_object is None and object_id:
try:
sub_object = object._getOb(object_id)
except (AttributeError, KeyError, TypeError):
......@@ -234,8 +234,8 @@ class ERP5TransactionConduit(TioSafeBaseConduit):
else:
for synchronization in synchronization_list:
# encode to the output type
if getattr(synchronization, 'getObjectFromGid', None) is not None:
link_object = synchronization.getObjectFromGid(b16encode(link_gid))
if getattr(synchronization, 'getDocumentFromGid', None) is not None:
link_object = synchronization.getDocumentFromGid(b16encode(link_gid))
#LOG("trying to get %s from %s, got %s" %(link_gid, synchronization.getPath(), link_object), 300, "This is for category type %s" %(category))
if link_object is not None:
break
......@@ -284,7 +284,7 @@ class ERP5TransactionConduit(TioSafeBaseConduit):
else:
synchronization_list = self.getSynchronizationObjectListForType(kw.get('domain'), 'Product', 'publication')
for synchronization in synchronization_list:
link_object = synchronization.getObjectFromGid(b16encode(link_gid))
link_object = synchronization.getDocumentFromGid(b16encode(link_gid))
if link_object is not None:
break
# in the worse case save the line with the unknown product
......
33
\ No newline at end of file
34
\ No newline at end of file
......@@ -321,7 +321,7 @@ class Transaction(TransactionBrain):
for erp5_sync in erp5_sync_list:
try:
resource = erp5_sync.getObjectFromGid(b16encode(resource_gid))
resource = erp5_sync.getDocumentFromGid(b16encode(resource_gid))
break
except (ValueError, AttributeError):
resource = None
......
......@@ -320,7 +320,7 @@ class Transaction(TransactionBrain):
for erp5_sync in erp5_sync_list:
try:
resource = erp5_sync.getObjectFromGid(b16encode(resource_gid))
resource = erp5_sync.getDocumentFromGid(b16encode(resource_gid))
break
except (ValueError, AttributeError):
resource = None
......
......@@ -1926,12 +1926,6 @@ class ERP5Generator(PortalGenerator):
addERP5Tool(p, 'portal_selections', 'Selection Tool')
addERP5Tool(p, 'portal_preferences', 'Preference Tool')
try:
# Add ERP5SyncML Tools
addERP5Tool(p, 'portal_synchronizations', 'Synchronization Tool')
except AttributeError:
pass
# Add Message Catalog
if not 'Localizer' in p.objectIds():
addLocalizer = p.manage_addProduct['Localizer'].manage_addLocalizer
......
......@@ -74,7 +74,6 @@ Simulation Movement
Simulation Tool
Standard Property
String Attribute Match Constraint
Synchronization Tool
System Preference
TALES Constraint
Template Tool
......
##############################################################################
#
# Copyright (c) 2012 Nexedi SA and Contributors. All Rights Reserved.
# Aurelien Calonne <aurel@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.
#
##############################################################################
class BaseConduit(object):
"""
A base class for conduit which implement the most generic methods
and also defined methods
It can be used as a conduit which does nothing (as a Conduit is always
required for now even in one way mode)
"""
def getContentType(self):
# This must be a sub-BaseConduit
return "text/xml"
def generateDiff(self, new_data, former_data):
return None
def getXMLFromObjectWithId(self, object, xml_mapping, context_document=None):
"""
return the xml with Id of Object
"""
# XXX to be renamed
data = ''
if not xml_mapping:
return data
data_generator_method = getattr(object, xml_mapping, None)
if data_generator_method:
try:
data = data_generator_method(context_document=context_document)
except TypeError:
# The method does not accept parameters (ie old-style)
data = data_generator_method()
return data
def getGidFromObject(self, object):
"""
return the Gid composed with the object informations
"""
# XXX to be renamed
return object.getId()
def replaceIdFromXML(self, xml, attribute_name, new_id, as_string=True):
# XXX To be renamed & review
return xml
def addNode(self, *args, **kw):
"""
Method call when a add command is received
It returns the list of conflict as well as the newly created document
This has to be reviewed, object is not required anylonger
"""
return {'conflict_list': [], 'object': None}
def updateNode(self, *args, **kw):
"""
Method called when update command is received
It returns the list of conflicts
"""
return []
def deleteNode(self, *args, **kw):
"""
Method called when update command is received
XXX It should returns the list of conflicts ?
"""
pass
......@@ -34,12 +34,10 @@ from Products.ERP5Type.XMLExportImport import MARSHALLER_NAMESPACE_URI
from Products.CMFCore.utils import getToolByName
from Products.ERP5Type.Base import WorkflowMethod
from DateTime.DateTime import DateTime
from email.mime.base import MIMEBase
from email import encoders
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, interfaces
from Products.ERP5Type.Globals import PersistentMapping
import pickle
from xml.sax.saxutils import unescape
import re
from lxml import etree
......@@ -47,11 +45,13 @@ from lxml.etree import Element
parser = etree.XMLParser(remove_blank_text=True)
from xml_marshaller.xml_marshaller import load_tree as unmarshaller
from xupdate_processor import xuproc
from zLOG import LOG, INFO, DEBUG
from base64 import standard_b64decode
from zope.interface import implements
from copy import deepcopy
import logging
syncml_logger = logging.getLogger('ERP5SyncML')
import sha
from Products.ERP5SyncML.SyncMLConstant import XUPDATE_ELEMENT,\
......@@ -167,8 +167,6 @@ class ERP5Conduit(XMLSyncUtilsMixin):
reset_workflow = False
conflict_list = []
xml = self.convertToXml(xml)
#LOG('ERP5Conduit.addNode', INFO, 'object path:%r' % object.getPath())
#LOG('ERP5Conduit.addNode', INFO, '\n%s' % etree.tostring(xml, pretty_print=True))
if xml is None:
return {'conflict_list': conflict_list, 'object': sub_object}
# In the case where this new node is a object to add
......@@ -207,7 +205,8 @@ class ERP5Conduit(XMLSyncUtilsMixin):
security.declareProtected(Permissions.ModifyPortalContent, 'deleteNode')
def deleteNode(self, xml=None, object=None, object_id=None, **kw):
"""
A node is deleted
This method manage the deletion of a node as well as the deletion
of one property
"""
#LOG('ERP5Conduit.deleteNode', INFO, 'object path:%s' % object.getPath())
#LOG('ERP5Conduit deleteNode', INFO, 'object_id:%r' % object_id)
......@@ -277,11 +276,10 @@ class ERP5Conduit(XMLSyncUtilsMixin):
"""
conflict_list = []
if xml is None:
return {'conflict_list':conflict_list, 'object':object}
return []
xml = self.convertToXml(xml)
#LOG('ERP5Conduit.updateNode, force: ', INFO, force)
#LOG('ERP5Conduit updateNode', INFO, object.getPath())
#LOG('ERP5Conduit updateNode', INFO, '\n%s' % etree.tostring(xml, pretty_print=True))
syncml_logger.debug("updateNode with xml %s"
% (etree.tostring(xml, pretty_print=True)))
if xml.tag == '{%s}modifications' % xml.nsmap.get('xupdate'):
conflict_list += self.applyXupdate(object=object,
xupdate=xml,
......@@ -311,7 +309,6 @@ class ERP5Conduit(XMLSyncUtilsMixin):
for subnode in xml:
if subnode.xpath('name()') == XUPDATE_ELEMENT:
keyword = subnode.get('name')
data_xml = subnode
else:
#XXX find something better than hardcoded prefix
# We can call add node
......@@ -340,7 +337,6 @@ class ERP5Conduit(XMLSyncUtilsMixin):
xpath_expression,
get_target_parent=get_target_parent)
data_type = context.getPropertyType(keyword)
#LOG('ERP5Conduit.updateNode', INFO, 'data_type:%r for keyword: %s' % (data_type, keyword))
data = self.convertXmlValue(xml, data_type=data_type)
args[keyword] = data
args = self.getFormatedArgs(args=args)
......@@ -352,9 +348,6 @@ class ERP5Conduit(XMLSyncUtilsMixin):
# - current_data : the data actually on this box
isConflict = False
if previous_xml and not force:
# if no previous_xml, no conflict
#old_data = self.getObjectProperty(keyword, previous_xml,
#data_type=data_type)
previous_xml_tree = self.convertToXml(previous_xml)
old_result = previous_xml_tree.xpath(xpath_expression)
if old_result:
......@@ -363,13 +356,8 @@ class ERP5Conduit(XMLSyncUtilsMixin):
raise ValueError('Xpath expression does not apply on previous'\
' xml:%r' % xpath_expression)
current_data = self.getProperty(context, keyword)
#LOG('ERP5Conduit.updateNode', INFO, 'Conflict keyword: %s' % keyword)
#LOG('ERP5Conduit.updateNode', INFO, 'Conflict data: %s' % str(data))
#LOG('ERP5Conduit.updateNode', INFO, 'Conflict old_data: %s' % str(old_data))
#LOG('ERP5Conduit.updateNode', INFO, 'Conflict current_data: %s' % str(current_data))
if (old_data != current_data) and (data != current_data) and\
keyword not in FORCE_CONFLICT_LIST:
#LOG('ERP5Conduit.updateNode', INFO, 'Conflict on : %s' % keyword)
# This is a conflict
isConflict = True
xml_string = etree.tostring(xml, encoding='utf-8')
......@@ -383,14 +371,23 @@ class ERP5Conduit(XMLSyncUtilsMixin):
if data_type not in DATA_TYPE_LIST:
conflict.edit(local_value=current_data,
remote_value=data)
syncml_logger.info("Generated a conflict for %s" % (keyword,))
conflict_list += [conflict]
else:
syncml_logger.info("UpdateNode : no previous xml founds or force")
# We will now apply the argument with the method edit
if args and (not isConflict or force) and (not simulate or reset):
syncml_logger.info("calling updateContent : %s" % (args))
self._updateContent(object=context, **args)
# It is sometimes required to do something after an edit
if getattr(context, 'manage_afterEdit', None) is not None:
context.manage_afterEdit()
else:
syncml_logger.warning("did not call updateContent on %s" % (context))
else:
syncml_logger.info("UpdateNode : not editable property %s" % (keyword))
# Specific cases of update
if keyword == 'object':
# This is the case where we have to call addNode
conflict_list += self.addNode(xml=xml,
......@@ -413,6 +410,8 @@ class ERP5Conduit(XMLSyncUtilsMixin):
conflict_list += self.addNode(xml=xml, object=object,
force=force, simulate=simulate,
reset=reset, **kw)['conflict_list']
else:
syncml_logger.warning("UpdateNode : not a property %s" % (etree.tostring(xml, pretty_print=True),))
return conflict_list
security.declareProtected(Permissions.AccessContentsInformation,
......@@ -507,7 +506,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
security.declareProtected(Permissions.AccessContentsInformation,
'isHistoryAdd')
def isHistoryAdd(self, xml):
bad_list = (HISTROY_EXP,)
bad_list = (HISTORY_EXP,)
value = xml.get('select')
if value is not None:
for bad_string in bad_list:
......@@ -561,10 +560,14 @@ class ERP5Conduit(XMLSyncUtilsMixin):
# copy of xml object for modification
xml = deepcopy(xml)
object_element = xml.find('object')
if attribute_name == 'id':
try:
del object_element.attrib['gid']
else:
except KeyError:
pass
try:
del object_element.attrib['id']
except KeyError:
pass
object_element.attrib[attribute_name] = new_id
if as_string:
return etree.tostring(xml, encoding="utf-8")
......@@ -815,10 +818,9 @@ class ERP5Conduit(XMLSyncUtilsMixin):
xupdate_builded = False
xpath_expression_update_dict = {}
for subnode in xupdate:
selection_name = ''
original_xpath_expression = subnode.get('select', '')
if not xupdate_builded and\
MARSHALLER_NAMESPACE_URI in subnode.nsmap.values()\
if not xupdate_builded and \
MARSHALLER_NAMESPACE_URI in subnode.nsmap.values() \
or 'block_data' in original_xpath_expression:
# It means that the xpath expression is targetting
# marshalled values or data nodes. We need to rebuild the original xml
......@@ -892,15 +894,14 @@ class ERP5Conduit(XMLSyncUtilsMixin):
**update_dict)
return conflict_list
def isWorkflowActionAddable(self, object=None, status=None, wf_tool=None,
wf_id=None, xml=None):
def isWorkflowActionAddable(self, document, status, wf_id):
"""
Some checking in order to check if we should add the workfow or not
We should not returns a conflict list as we wanted before, we should
instead go to a conflict state.
"""
# We first test if the status in not already inside the workflow_history
wf_history = object.workflow_history
wf_history = document.workflow_history
if wf_id in wf_history:
action_list = wf_history[wf_id]
else:
......@@ -910,9 +911,11 @@ class ERP5Conduit(XMLSyncUtilsMixin):
for action in action_list:
this_one = WORKFLOW_ACTION_ADDABLE
if time <= action.get('time'):
# action in the past are not append
# action in the past are not appended
addable = WORKFLOW_ACTION_INSERTABLE
for key in action.keys():
key_list = action.keys()
key_list.remove("time")
for key in key_list:
if status[key] != action[key]:
this_one = WORKFLOW_ACTION_NOT_ADDABLE
break
......@@ -928,7 +931,8 @@ class ERP5Conduit(XMLSyncUtilsMixin):
This is really usefull if you want to write your
own Conduit.
"""
#LOG('ERP5Conduit.addNode',0,'portal_type: |%s|' % str(portal_type))
from zLOG import LOG
LOG('ERP5Conduit.addNode',0,'portal_type: |%s|' % str(portal_type))
subobject = object.newContent(portal_type=portal_type, id=object_id)
return subobject, True, True
......@@ -948,10 +952,9 @@ class ERP5Conduit(XMLSyncUtilsMixin):
#for action in self.getWorkflowActionFromXml(xml):
status = self.getStatusFromXml(xml)
#LOG('addNode, status:',0,status)
add_action = self.isWorkflowActionAddable(object=object,
status=status,wf_tool=wf_tool,
wf_id=wf_id,xml=xml)
add_action = self.isWorkflowActionAddable(document=object,
status=status,
wf_id=wf_id,)
if not simulate:
if add_action == WORKFLOW_ACTION_ADDABLE:
wf_tool.setStatusOf(wf_id, object, status)
......@@ -1017,6 +1020,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
This is the default editDocument method. This method
can easily be overwritten.
"""
syncml_logger.debug("editing document %s with %s" % (object, kw))
object._edit(**kw)
security.declareProtected(Permissions.ModifyPortalContent, 'getProperty')
......@@ -1047,6 +1051,9 @@ class ERP5Conduit(XMLSyncUtilsMixin):
"""
# XXX We can not find an object with remote id
if object_id is None:
# XXX object must be retrieved by their GID, id must not be synchronised
# This hack is wrong, unfortunately all units are based on it so I can
# not remove it, all must be reviewed before
object_id = xml.get('id')
if object_id is not None:
if sub_object is None:
......@@ -1083,19 +1090,21 @@ class ERP5Conduit(XMLSyncUtilsMixin):
"""
return 'text/xml'
def generateDiff(self, old_data, new_data):
def generateDiff(self, new_data, former_data):
"""return xupdate node
"""
return getXupdateObject(old_data, new_data)
return getXupdateObject(new_data, former_data)
def applyDiff(self, original_data, diff):
"""Use xuproc for computing patched content
"""
# XXX xuproc does not support passing
# etree objetcs
if not isinstance(diff, basestring):
diff = etree.tostring(diff)
return etree.tostring(xuproc.applyXUpdate(xml_xu_string=diff,
xml_doc_string=original_data), encoding='utf-8')
xml_doc_string=original_data),
encoding='utf-8')
# def getGidFromXML(self, xml, namespace, gid_from_xml_list):
# """
......
......@@ -27,11 +27,10 @@
#
##############################################################################
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from zLOG import LOG
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
class ERP5ConduitTitleGid(ERP5Conduit):
"""
......
......@@ -34,6 +34,10 @@ from AccessControl import ClassSecurityInfo
# Declarative security
security = ClassSecurityInfo()
WORKFLOW_ACTION_NOT_ADDABLE = 0
WORKFLOW_ACTION_ADDABLE = 1
WORKFLOW_ACTION_INSERTABLE = 2
class ERP5DocumentConduit(ERP5Conduit):
"""
ERP5DocumentConduit specialise generic Conduit
......@@ -52,3 +56,36 @@ class ERP5DocumentConduit(ERP5Conduit):
return "%s-%s-%s" %\
(object.getReference(), object.getVersion(), object.getLanguage())
def isWorkflowActionAddable(self, document, status, wf_id):
"""
Some checking in order to check if we should add the workfow or not
We should not returns a conflict list as we wanted before, we should
instead go to a conflict state.
"""
# We first test if the status in not already inside the workflow_history
wf_history = document.workflow_history
if wf_id in wf_history:
action_list = wf_history[wf_id]
else:
return WORKFLOW_ACTION_ADDABLE
addable = WORKFLOW_ACTION_ADDABLE
time = status.get('time')
for action in action_list:
this_one = WORKFLOW_ACTION_ADDABLE
# if time <= action.get('time'):
# # action in the past are not appended
# addable = WORKFLOW_ACTION_INSERTABLE
key_list = action.keys()
# key_list.remove("time")
# XXX-AUREL For document it seems that checking time != is required
# I don't know why ?
for key in key_list:
if status[key] != action[key]:
this_one = WORKFLOW_ACTION_NOT_ADDABLE
break
if this_one:
addable = WORKFLOW_ACTION_NOT_ADDABLE
break
return addable
......@@ -27,21 +27,16 @@
#
##############################################################################
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from Products.CMFCore.utils import getToolByName
from Acquisition import aq_base, aq_inner, aq_chain, aq_acquire
from xml.dom import implementation
from xml.dom.ext import PrettyPrint
from xml.dom import Node
import random
from cStringIO import StringIO
from AccessControl import ClassSecurityInfo
from zLOG import LOG
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from Products.ERP5Type import Permissions
from Products.CMFCore.utils import getToolByName
class ERP5ShopOrderConduit(ERP5Conduit):
......@@ -51,19 +46,12 @@ class ERP5ShopOrderConduit(ERP5Conduit):
Don't forget to add this base categories in portal_category :
'hd_size', 'memory_size', 'optical_drive', 'keyboard_layout', 'cpu_type'
"""
# TODO: tester ce script sur le serveur de backup (qui semble être different)
# Declarative security
security = ClassSecurityInfo()
# Initialize the random function
random.seed()
security.declareProtected(Permissions.ModifyPortalContent, 'constructContent')
def constructContent(self, object, object_id, docid, portal_type):
"""
......
......@@ -27,22 +27,21 @@
#
##############################################################################
from Products.ERP5SyncML.Conduit.VCardConduit import VCardConduit
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from zLOG import LOG, INFO
from Products.ERP5SyncML.Conduit.VCardConduit import VCardConduit
class SharedVCardConduit(VCardConduit):
"""
A conduit is in charge to read data from a particular structure,
and then to save this data in another structure.
SharedVCardConduit is a peace of code who provide GID.
SharedVCardConduit is a piece of code who provide GID.
This GID are the same for all subscriber, so a same object could be updated
by all the subscriber.
"""
# Declarative security
security = ClassSecurityInfo()
......@@ -56,18 +55,15 @@ class SharedVCardConduit(VCardConduit):
gid_list.append('_')
if object.getLastName():
gid_list.append(object.getLastName())
sql_kw = {}
sql_kw['portal_type'] = 'Person'
sql_kw['title'] = object.getTitle()
sql_kw['id'] = {'query': object.getId(), 'range': 'max'}
sql_kw = {'portal_type' : 'Person',
'title' : object.getTitle(),
'id' : {'query': object.getId(), 'range': 'max'}
}
results = object.portal_catalog.countResults(**sql_kw)[0][0]
#LOG('getGidFromObject', INFO, 'getId:%s, getTitle:%s' % (object.getId(), object.getTitle()))
#LOG('getGidFromObject, number of results :', INFO, results)
if int(results) > 0:
gid_list.append('__')
gid_list.append(str(int(results)+1))
gid = ''.join(gid_list)
#LOG('getGidFromObject gid :', INFO, gid)
return gid
def getGidFromXML(self, vcard, gid_from_xml_list):
......@@ -82,15 +78,11 @@ class SharedVCardConduit(VCardConduit):
if vcard_dict.get('last_name'):
gid_from_vcard_list.append(vcard_dict['last_name'])
gid_from_vcard = ''.join(gid_from_vcard_list)
#LOG('getGidFromXML, gid_from_vcard :', INFO, gid_from_vcard)
number = len([item for item in gid_from_xml_list if item.startswith(gid_from_vcard)])
#LOG('getGidFromXML, gid_from_xml_list :', INFO, gid_from_xml_list)
#LOG('getGidFromXML, number :', INFO, number)
if number:
gid_from_vcard_list.append('__')
gid_from_vcard_list.append(str(number+1))
#it's mean for 3 persons a a a, the gid will be
#a_, a___2 a___3
gid_from_vcard = ''.join(gid_from_vcard_list)
#LOG('getGidFromXML, returned gid_from_vcard :', INFO, gid_from_vcard)
return gid_from_vcard
......@@ -135,9 +135,9 @@ class VCardConduit(ERP5Conduit):
"""
encoding=''
for item in property_parameters_list :
if ENCODING in item:
encoding = item['ENCODING']
# for item in property_parameters_list :
# if ENCODING in item:
# encoding = item['ENCODING']
property_value_list_well_incoded=[]
if encoding == 'QUOTED-PRINTABLE':
......@@ -224,11 +224,11 @@ class VCardConduit(ERP5Conduit):
"""
return 'text/vcard'
def generateDiff(self, old_data, new_data):
def generateDiff(self, new_data, former_data):
"""return unified diff for plain-text documents
"""
diff = '\n'.join(difflib.unified_diff(old_data.splitlines(),
new_data.splitlines()))
diff = '\n'.join(difflib.unified_diff(new_data.splitlines(),
former_data.splitlines()))
return diff
......
......@@ -28,7 +28,6 @@
##############################################################################
from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName
from Products.ERP5Type.Base import Base
from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet
......@@ -55,70 +54,123 @@ class SyncMLConflict(Base):
, PropertySheet.SyncMLConflict )
def _getPortalSynchronizationTool(self):
return getToolByName(self.getPortalObject(), 'portal_synchronizations')
return self.getPortalObject().portal_synchronizations
security.declareProtected(Permissions.AccessContentsInformation,
'applyPublisherValue')
def applyPublisherValue(self):
"""
after a conflict resolution, we have decided
to keep the local version of this object
XXX-AUREL : Comment to be fixed
"""
p_sync = self._getPortalSynchronizationTool()
p_sync.applyPublisherValue(self)
document = self.getOriginValue()
subscriber = self.getSubscriber()
gid = subscriber.getGidFromObject(document)
signature = subscriber.getSignatureFromGid(gid)
signature.delConflict(self)
if not signature.getConflictList():
signature.resolveConflictWithMerge()
security.declareProtected(Permissions.AccessContentsInformation,
'applyPublisherDocument')
def applyPublisherDocument(self):
"""
after a conflict resolution, we have decided
to keep the local version of this object
XXX-AUREL : Comment to be fixed
"""
p_sync = self._getPortalSynchronizationTool()
p_sync.applyPublisherDocument(self)
subscriber = self.getSubscriber()
for c in self._getPortalSynchronizationTool().getConflictList(
self.getOriginValue()):
if c.getSubscriber() == subscriber:
c.applyPublisherValue()
security.declareProtected(Permissions.AccessContentsInformation,
'getPublisherDocument')
def getPublisherDocument(self):
"""
after a conflict resolution, we have decided
to keep the local version of this object
XXX-AUREL : Comment to be fixed
"""
p_sync = self._getPortalSynchronizationTool()
return p_sync.getPublisherDocument(self)
return self.getOriginValue()
security.declareProtected(Permissions.AccessContentsInformation,
'getPublisherDocumentPath')
def getPublisherDocumentPath(self):
"""
after a conflict resolution, we have decided
to keep the local version of this object
XXX-AUREL : Comment to be fixed
"""
p_sync = self._getPortalSynchronizationTool()
return p_sync.getPublisherDocumentPath(self)
return self.getOrigin()
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriberDocument')
def getSubscriberDocument(self):
"""
after a conflict resolution, we have decided
to keep the local version of this object
XXX-AUREL : Comment to be fixed
"""
p_sync = self._getPortalSynchronizationTool()
return p_sync.getSubscriberDocument(self)
return self.unrestrictedTraverse(
self.getSubscriberDocumentPath())
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriberDocumentPath')
def getSubscriberDocumentPath(self):
"""
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = self._getPortalSynchronizationTool()
return p_sync.getSubscriberDocumentPath(self)
XXX-AUREL : Comment to be fixed
"""
subscriber = self.getSubscriber()
publisher_object = self.getOriginValue()
conduit = subscriber.getConduit()
publisher_xml = conduit.getXMLFromObjectWithId(publisher_object,
xml_mapping=subscriber.getXmlBindingGeneratorMethodId(),
context_document=subscriber.getPath())
directory = publisher_object.aq_inner.aq_parent
object_id = self._getPortalSynchronizationTool()._getCopyId(publisher_object)
conduit.addNode(xml=publisher_xml, object=directory, object_id=object_id,
signature=self.getParentValue())
subscriber_document = directory._getOb(object_id)
for c in self._getPortalSynchronizationTool().getConflictList(
self.getOriginValue()):
if c.getSubscriber() == subscriber:
c.applySubscriberValue(document=subscriber_document)
copy_path = subscriber_document.getPhysicalPath()
return copy_path
security.declareProtected(Permissions.ModifyPortalContent,
'applySubscriberDocument')
def applySubscriberDocument(self):
"""
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = self._getPortalSynchronizationTool()
p_sync.applySubscriberDocument(self)
def applySubscriberValue(self, object=None):
"""
get the domain
"""
p_sync = self._getPortalSynchronizationTool()
p_sync.applySubscriberValue(self, object=object)
XXX Comment to be fixed
"""
# XXX-AUREL : when we solve one conflict, it solves all conflicts related
# to the same object ? is it the wanted behaviour ?
subscriber = self.getSubscriber()
for c in self._getPortalSynchronizationTool().getConflictList(
self.getOriginValue()):
if c.getSubscriber() == subscriber:
c.applySubscriberValue()
security.declareProtected(Permissions.ModifyPortalContent,
'applySubscriberValue')
def applySubscriberValue(self, document=None):
"""
XXX Comment to be fixed
"""
solve_conflict = 1
if not document:
document = self.getOriginValue()
else:
# This means an object was given, this is used in order
# to see change on a copy, so don't solve conflict
solve_conflict = False
subscriber = self.getSubscriber()
# get the signature:
gid = subscriber.getGidFromObject(document)
signature = subscriber.getSignatureFromGid(gid)
# Import the conduit and get it
conduit = subscriber.getConduit()
conduit.updateNode(xml=self.getDiffChunk(), object=document,
force=True, signature=signature)
if solve_conflict:
signature.delConflict(self)
if not signature.getConflictList():
signature.resolveConflictWithMerge()
def getSubscriber(self):
"""
......
......@@ -34,12 +34,13 @@ from Products.ERP5SyncML.SyncMLConstant import ACTIVITY_PRIORITY
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
class SyncMLPublication(SyncMLSubscription):
"""Reply to request from SyncML clients,
"""
Reply to request from SyncML clients,
Serve data to be synchronized.
"""
meta_type = 'ERP5 Publication'
portal_type = 'SyncML Publication' # may be useful in the future...
portal_type = 'SyncML Publication'
# Declarative security
security = ClassSecurityInfo()
......@@ -48,20 +49,17 @@ class SyncMLPublication(SyncMLSubscription):
'getSubscriber')
def getSubscriber(self, subscription_url):
"""
return the subscriber corresponding the to subscription_url
Return the subscriber corresponding the to subscription_url
"""
subscriber = None
for subscription in self.contentValues(portal_type='SyncML Subscription'):
if subscription.getSubscriptionUrlString() == subscription_url:
subscriber = subscription
break
return subscriber
if subscription.getUrlString() == subscription_url:
return subscription
security.declareProtected(Permissions.AccessContentsInformation,
'getSubscriberList')
def getSubscriberList(self):
"""
Get the list of subscribers
Return the list of subscribers
"""
return self.contentValues(portal_type='SyncML Subscription')
......@@ -71,6 +69,7 @@ class SyncMLPublication(SyncMLSubscription):
"""
Reset all subscribers
"""
# XXX See case with lot of signature
self.activate(activity='SQLQueue',
priority=ACTIVITY_PRIORITY).manage_delObjects(ids=list(self.getObjectIds()))
......
......@@ -27,34 +27,31 @@
#
##############################################################################
from hashlib import md5
from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet
from zLOG import LOG, DEBUG, INFO
from Products.ERP5SyncML.Utils import PdataHelper
from hashlib import md5
_MARKER = []
class SyncMLSignature(XMLObject):
"""
status -- SENT, CONFLICT...
md5_object -- An MD5 value of a given document
#uid -- The UID of the document
id -- the ID of the document
gid -- the global id of the document
rid -- the uid of the document on the remote database,
only needed on the server.
xml -- the xml of the object at the time where it was synchronized
A Signature represent a document that is synchronized
It contains as attribute the xml representation of the object which is used
to generate the diff of the object between two synchronization
It also contains the list of conflict as sub-objects when it happens
"""
meta_type = 'ERP5 Signature'
portal_type = 'SyncML Signature'
isIndexable = ConstantGetter('isIndexable', value=False)
# Make sure RAD generated accessors at the class level
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
......@@ -72,12 +69,11 @@ class SyncMLSignature(XMLObject):
security.declareProtected(Permissions.ModifyPortalContent, 'setData')
def setData(self, value):
"""
set the XML corresponding to the object
Set the XML corresponding to the object
"""
if value:
# convert the string to Pdata
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
#data, size = pdata_wrapper()
self._setData(pdata_wrapper)
self.setTemporaryData(None) # We make sure that the data will not be erased
self.setContentMd5(pdata_wrapper.getContentMd5())
......@@ -88,11 +84,11 @@ class SyncMLSignature(XMLObject):
security.declareProtected(Permissions.AccessContentsInformation, 'getData')
def getData(self, default=_MARKER):
"""
get the XML corresponding to the object
Get the XML corresponding to the object
"""
if self.hasData():
return str(self._baseGetData())
if default is _MARKER:
elif default is _MARKER:
return self._baseGetData()
else:
return self._baseGetData(default)
......@@ -106,8 +102,7 @@ class SyncMLSignature(XMLObject):
the confirmation of synchronization
"""
if value:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setTemporaryData(pdata_wrapper)
self._setTemporaryData(PdataHelper(self.getPortalObject(), value))
else:
self._setTemporaryData(None)
......@@ -115,11 +110,11 @@ class SyncMLSignature(XMLObject):
'getTemporaryData')
def getTemporaryData(self, default=_MARKER):
"""
get the temp xml
Return the temp xml as string
"""
if self.hasTemporaryData():
return str(self._baseGetTemporaryData())
if default is _MARKER:
elif default is _MARKER:
return self._baseGetTemporaryData()
else:
return self._baseGetTemporaryData(default)
......@@ -127,9 +122,9 @@ class SyncMLSignature(XMLObject):
security.declareProtected(Permissions.AccessContentsInformation, 'checkMD5')
def checkMD5(self, xml_string):
"""
check if the given md5_object returns the same things as
the one stored in this signature, this is very usefull
if we want to know if an objects has changed or not
Check if the given md5_object returns the same things as the one stored in
this signature, this is very usefull if we want to know if an objects has
changed or not
Returns 1 if MD5 are equals, else it returns 0
"""
if isinstance(xml_string, unicode):
......@@ -139,10 +134,9 @@ class SyncMLSignature(XMLObject):
security.declareProtected(Permissions.ModifyPortalContent, 'setPartialData')
def setPartialData(self, value):
"""
Set the partial string we will have to
deliver in the future
Set the partial string we will have to deliver in the future
"""
if value is not None:
if value:
if not isinstance(value, PdataHelper):
value = PdataHelper(self.getPortalObject(), value)
self._setPartialData(value)
......@@ -154,13 +148,11 @@ class SyncMLSignature(XMLObject):
security.declareProtected(Permissions.ModifyPortalContent, 'setLastData')
def setLastData(self, value):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
This is the xml temporarily saved, it will be stored with setXML when we
will receive the confirmation of synchronization
"""
if value is not None:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setLastData(pdata_wrapper)
if value:
self._setLastData(PdataHelper(self.getPortalObject(), value))
else:
self._setLastData(None)
......@@ -168,21 +160,22 @@ class SyncMLSignature(XMLObject):
'getPartialData')
def getPartialData(self, default=_MARKER):
"""
get the patial xml
Return the patial xml as string
"""
if self.hasPartialData():
return str(self._baseGetPartialData())
if default is _MARKER:
elif default is _MARKER:
return self._baseGetPartialData()
else:
return self._baseGetPartialData(default)
security.declareProtected(Permissions.ModifyPortalContent, 'appendPartialData')
security.declareProtected(Permissions.ModifyPortalContent,
'appendPartialData')
def appendPartialData(self, value):
"""
Append the partial string we will have to deliver in the future
"""
if value is not None:
if value:
if not isinstance(value, PdataHelper):
value = PdataHelper(self.getPortalObject(), value)
last_data = value.getLastPdata()
......@@ -194,36 +187,11 @@ class SyncMLSignature(XMLObject):
self.setPartialData(value)
self.setLastDataPartialData(last_data)
#security.declareProtected(Permissions.AccessContentsInformation,
#'getFirstChunkPdata')
#def getFirstChunkPdata(self, size_lines):
#"""
#"""
#chunk = [self.getPartialData().data]
#size = chunk[0].count('\n')
#current = self.getPartialData()
#next = current.next
#while size < size_lines and next is not None:
#current = next
#size += current.data.count('\n')
#chunk.append(current.data)
#next = current.next
#if size == size_lines:
#self.setPartialData(next)
#elif size > size_lines:
#overflow = size - size_lines
#data_list = chunk[-1].split('\n')
#chunk[-1] = '\n'.join(data_list[:-overflow])
#current.data = '\n'.join(data_list[-overflow:])
#self.setPartialData(current)
#return ''.join(chunk)
security.declareProtected(Permissions.ModifyPortalContent,
'getFirstPdataChunk')
def getFirstPdataChunk(self, max_len):
"""
"""
#chunk, rest_in_queue = self._baseGetPartialData().\
#getFirstPdataChunkAndRestInQueue(max_len)
partial_data = self._baseGetPartialData()
chunk = partial_data[:max_len]
rest_in_queue = partial_data[max_len:]
......@@ -235,13 +203,11 @@ class SyncMLSignature(XMLObject):
'setSubscriberXupdate')
def setSubscriberXupdate(self, value):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
This is the xml temporarily saved, it will be stored with setXML when we
will receive the confirmation of synchronization
"""
if value is not None:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setSubscriberXupdate(pdata_wrapper)
if value:
self._setSubscriberXupdate(PdataHelper(self.getPortalObject(), value))
else:
self._setSubscriberXupdate(None)
......@@ -249,11 +215,11 @@ class SyncMLSignature(XMLObject):
'getSubscriberXupdate')
def getSubscriberXupdate(self, default=_MARKER):
"""
get the patial xml
Return the patial xml as string
"""
if self.hasSubscriberXupdate():
return str(self._baseGetSubscriberXupdate())
if default is _MARKER:
elif default is _MARKER:
return self._baseGetSubscriberXupdate()
else:
return self._baseGetSubscriberXupdate(default)
......@@ -262,13 +228,11 @@ class SyncMLSignature(XMLObject):
'setPublisherXupdate')
def setPublisherXupdate(self, value):
"""
This is the xml temporarily saved, it will
be stored with setXML when we will receive
the confirmation of synchronization
This is the xml temporarily saved, it will be stored with setXML when we
will receive the confirmation of synchronization
"""
if value is not None:
pdata_wrapper = PdataHelper(self.getPortalObject(), value)
self._setPublisherXupdate(pdata_wrapper)
if value:
self._setPublisherXupdate(PdataHelper(self.getPortalObject(), value))
else:
self._setPublisherXupdate(None)
......@@ -276,67 +240,61 @@ class SyncMLSignature(XMLObject):
'getPublisherXupdate')
def getPublisherXupdate(self, default=_MARKER):
"""
get the patial xml
Return the partial xml as string
"""
if self.hasPublisherXupdate():
return str(self._baseGetPublisherXupdate())
if default is _MARKER:
elif default is _MARKER:
return self._baseGetPublisherXupdate()
else:
return self._baseGetPublisherXupdate(default)
security.declareProtected(Permissions.ModifyPortalContent,
'reset')
def reset(self):
"""Clear Signature and change validation_state to not_synchronized
"""
def reset(self, no_conflict=False):
"""
Clear Signature and change validation_state to not_synchronized
no_conflict : prevent the reset of signature for which conflict
has not been marked resolved, this is usefull when
resetting all signature at the beginning of a sync process
XXX Use a better name and a positive value by default
"""
if no_conflict and self.getValidationState() in (
'conflict',
'conflict_resolved_with_merge',
'conflict_resolved_with_client_command_winning'):
return
if self.getValidationState() != 'not_synchronized':
self.drift()
self.setPartialData(None)
self.setTemporaryData(None)
security.declareProtected(Permissions.ModifyPortalContent,
'getConflictList')
def getConflictList(self):
"""
Return the actual action for a partial synchronization
"""
return self.contentValues()
# returned_conflict_list = []
# if getattr(self, 'conflict_list', None) is None:
# return returned_conflict_list
# if len(self.conflict_list)>0:
# returned_conflict_list.extend(self.conflict_list)
# return returned_conflict_list
security.declareProtected(Permissions.ModifyPortalContent,
'setConflictList')
def setConflictList(self, conflict_list):
"""
Return the actual action for a partial synchronization
XXX is it still usefull ?
"""
return
# if conflict_list is None or conflict_list == []:
# self.resetConflictList()
# else:
# self.conflict_list = conflict_list
security.declareProtected(Permissions.ModifyPortalContent,
'resetConflictList')
def resetConflictList(self):
"""
Return the actual action for a partial synchronization
XXX is it still usefull ?
"""
return
#self.conflict_list = PersistentMapping()
security.declareProtected(Permissions.ModifyPortalContent,
'delConflict')
def delConflict(self, conflict):
"""
Delete provided conflict object
"""
self.manage_delObjects([conflict.getId(),])
# conflict_list = []
# for c in self.getConflictList():
# #LOG('delConflict, c==conflict',0,c==aq_base(conflict))
# if c != aq_base(conflict):
# conflict_list += [c]
# if conflict_list != []:
# self.setConflictList(conflict_list)
# else:
# self.resetConflictList()
......@@ -27,62 +27,48 @@
#
##############################################################################
from base64 import b16encode, b16decode
from warnings import warn
from logging import getLogger
from urlparse import urlparse
from lxml import etree
from copy import deepcopy
from AccessControl import ClassSecurityInfo
from Acquisition import aq_base
from AccessControl.SecurityManagement import newSecurityManager
from DateTime import DateTime
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.Utils import deprecated
from DateTime import DateTime
from zLOG import LOG, DEBUG, INFO, WARNING
from base64 import b16encode, b16decode
from Products.ERP5SyncML.XMLSyncUtils import getConduitByName
from Products.ERP5SyncML.XMLSyncUtils import getConduitByName, \
buildAnchorFromDate
from Products.ERP5SyncML.SyncMLConstant import MAX_OBJECTS, ACTIVITY_PRIORITY,\
NULL_ANCHOR
from Products.ERP5SyncML.SyncMLMessage import SyncMLResponse
from Products.ERP5SyncML.Transport.HTTP import HTTPTransport
from Products.ERP5SyncML.Transport.File import FileTransport
from Products.ERP5SyncML.Transport.Mail import MailTransport
from Products.ERP5SyncML.SyncMLConstant import MAX_LEN, ADD_ACTION, \
REPLACE_ACTION
from Products.ERP5SyncML.XMLSyncUtils import cutXML
from Products.ERP5SyncML.SyncMLConstant import MAX_OBJECTS, ACTIVITY_PRIORITY
from warnings import warn
transport_scheme_dict = {
"http" : HTTPTransport(),
"https" : HTTPTransport(),
"file" : FileTransport(),
"mail" : MailTransport(),
}
syncml_logger = getLogger('ERP5SyncML')
MAX_OBJECT_PER_MESSAGE = 300
_MARKER = []
class SyncMLSubscription(XMLObject):
"""
Subscription hold the definition of a master ODB
from/to which a selection of objects will be synchronized
Subscription defined by::
publication_url -- a URI to a publication
subscription_url -- URL of ourselves
destination_path -- the place where objects are stored
query -- a query which defines a local set of documents which
are going to be synchronized
xml_mapping -- a PageTemplate to map documents to XML
gpg_key -- the name of a gpg key to use
Subscription also holds private data to manage
the synchronization. We choose to keep an MD5 value for
all documents which belong to the synchronization process::
signatures -- a dictionnary which contains the signature
of documents at the time they were synchronized
session_id -- it defines the id of the session
with the server.
last_anchor - it defines the id of the last synchronization
next_anchor - it defines the id of the current synchronization
Subscription inherit of File because the Signature use method _read_data
which have the need of a __r_jar not None.
During the initialization of a Signature this __p_jar is None
"""
meta_type = 'ERP5 Subscription'
portal_type = 'SyncML Subscription' # may be useful in the future...
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
......@@ -99,73 +85,741 @@ class SyncMLSubscription(XMLObject):
, PropertySheet.SyncMLSubscription
, PropertySheet.SyncMLSubscriptionConstraint )
security.declareProtected(Permissions.AccessContentsInformation,
'isOneWayFromServer')
def isOneWayFromServer(self):
return self.getPortalType() == 'SyncML Subscription' and \
self.getSyncmlAlertCode() == 'one_way_from_server'
security.declarePrivate('finishSynchronization')
def finishSynchronization(self,):
"""
Last method call that will make sure to finish the sync process
and reset all necessary variable
"""
self.finish() # Worflow transition
syncml_logger.info('--- synchronization ended on the server side ---')
if self.getAuthenticationState() == 'logged_in':
self.logout()
self._edit(authenticated_user=None)
security.declareProtected(Permissions.AccessContentsInformation,
'isOneWayFromClient')
def isOneWayFromClient(self):
return self.getParentValue().getPortalType() == 'SyncML Publication' and \
self.getSyncmlAlertCode() == 'one_way_from_client'
security.declarePrivate('getAndActivate')
def getAndActivate(self, callback, method_kw, activate_kw, **kw):
"""
This methods is called by the asynchronous engine to split activity
generation into activities.
security.declareProtected(Permissions.AccessContentsInformation,
'getSynchronizationType')
def getSynchronizationType(self, default=_MARKER):
"""Deprecated alias of getSyncmlAlertCode
callback : method to call in activity
method_kw : callback's parameters
activate_kw : activity parameters to pass to activate call
kw : any parameter getAndActivate can required if it calls itself
Last activate must wait for all other activities to be processed in order
to set the Final tag in the message, this is required by SyncML DS
specification
"""
warn('Use getSyncmlAlertCode instead', DeprecationWarning)
if default is _MARKER:
code = self.getSyncmlAlertCode()
# The following implementation is base on CatalogTool.searchAndActivate
# It might be possible to move a part of this code into the domain class
# so that it can be configurable as not all backend are optimised for
# this default implementation
search_kw = dict(kw)
packet_size = search_kw.pop('packet_size', 30)
limit = packet_size * search_kw.pop('activity_count', 100)
try:
r = self.getDocumentIdList(limit=limit, **search_kw) # It is assumed that
# the result is sorted
except TypeError:
syncml_logger.warning("Script %s does not accept paramaters limit=%s kw=%s" %
(self.getListMethodId(), limit, search_kw,))
r = self.getDocumentList() # It is assumed that
# the result is sorted
result_count = len(r)
generated_other_activity = False
if result_count:
syncml_logger.debug("getAndActivate : got %d result, limit = %d, packet %d" %
(result_count, limit, packet_size))
if result_count == limit:
# Recursive call to prevent too many activity generation
next_kw = dict(activate_kw, priority=1+activate_kw.get('priority', 1))
kw["min_id"] = r[-1].getId()
syncml_logger.debug("--> calling getAndActivate in activity, min = %s" %
(kw["min_id"],))
self.activate(**next_kw).getAndActivate(
callback, method_kw, activate_kw, **kw)
generated_other_activity = True
r = [x.getId() for x in r]
message_id_list = self.getNextMessageIdList(id_count=result_count)
# XXX maybe (result_count / packet_size) + 1 instead of result_count
message_id_list.reverse() # We pop each id in the following loop
activate = self.getPortalObject().portal_synchronizations.activate
callback_method = getattr(activate(**activate_kw), callback)
if generated_other_activity:
for i in xrange(0, result_count, packet_size):
syncml_logger.debug("-- getAndActivate : recursive call, generating for %s"
% (r[i:i+packet_size],))
callback_method(id_list=r[i:i+packet_size],
message_id=message_id_list.pop(),
activate_kw=activate_kw,
**method_kw)
else:
code = self.getSyncmlAlertCode(default=default)
return code
i = 0
for i in xrange(0, result_count-packet_size, packet_size):
syncml_logger.debug("-- getAndActivate : call, generating for %s : %s" %
(r[i:i+packet_size], activate_kw))
callback_method(id_list=r[i:i+packet_size],
message_id=message_id_list.pop(),
activate_kw=activate_kw,
**method_kw)
# Final activity must be executed after all other
syncml_logger.debug("---- getAndActivate : final call for %s : %s" %(r[i+packet_size:], activate_kw))
callback_method(id_list=r[i+packet_size:], # XXX Has to be unit tested
# with mock object
message_id=message_id_list.pop(),
activate_kw=activate_kw,
**method_kw)
return result_count
security.declarePrivate('checkCorrectRemoteSessionId')
def checkCorrectRemoteSessionId(self, session_id):
security.declarePrivate('sendMessage')
def sendMessage(self, xml):
"""
We will see if the last session id was the same
wich means that the same message was sent again
Send the syncml response according to the protocol defined for the target
"""
# First register sent message in case we received same message multiple time
# XXX-must be check according to specification
# XXX-performance killer in scalable environment
# XXX maybe use memcached instead for this ?
# self.setLastSentMessage(xml)
# XXX must review all of this
# - source/target must be relative URI (ie
# portal_synchronizations/person_pub) so that there is no need to defined
# source_reference
# -content type must be get from SyncMLMessage directly
# SyncML can transmit xml or wbxml, transform the xml when required
# XXX- This must be manager in syncml message representation
to_url = self.getUrlString()
scheme = urlparse(to_url)[0]
if self.getIsSynchronizedWithErp5Portal() and scheme in ("http", "https"):
# XXX will be removed soon
to_url = self.getUrlString() + '/portal_synchronizations/readResponse'
# call the transport to send data
transport_scheme_dict[scheme].send(to_url=to_url, data=xml,
sync_id=self.getDestinationReference(),
content_type=self.getContentType())
return True if the session id was not seen, False if already seen
def _loginUser(self, user_id=None):
"""
if self.getLastSessionId() == session_id:
return False
self.setLastSessionId(session_id)
return True
Log in with the user provided or defined on self
"""
if not user_id:
user_id = self.getProperty('authenticated_user')
if user_id:
# TODO: make it work for users existing anywhere
user_folder = self.getPortalObject().acl_users
user = user_folder.getUserById(user_id).__of__(user_folder) # __of__ might got AttributeError
if user is None:
raise ValueError("User %s cannot be found in user folder, \
synchronization cannot work with this kind of user" % (user_id,))
else:
newSecurityManager(None, user)
else:
raise ValueError(
"Impossible to find a user to log in, subscription = %s"
% (self.getRelativeUrl()))
security.declarePrivate('checkCorrectRemoteMessageId')
def checkCorrectRemoteMessageId(self, message_id):
# XXX To be done later
def _applyAddCommand(self,):
"""
We will see if the last message id was the same
wich means that the same message was sent again
Apply the add command received, when document already exits, we
do a kind of "Replace" command instead
"""
pass
return True if the message id was not seen, False if already seen
security.declarePrivate('applySyncCommand')
def applySyncCommand(self, action, request_message_id, syncml_response,
simulate=False):
"""
if self.getLastMessageId() == message_id:
return False
self.setLastMessageId(message_id)
return True
Apply a sync command received
Here is the main algorithm :
- try to get the signature for the GID ( some mode does not required it)
- apply the action
- update signature
- generate the status command
"""
conduit = self.getConduit()
destination = self.getSourceValue()
conflict_list = []
status_code = 'success'
# First retrieve the GID of the object we want to modify
gid = action["source"] or action["target"]
# Retrieve the signature for the current GID
signature = self.getSignatureFromGid(gid)
if syncml_response is not None: # No response to send when no signature to create
document = self.getDocumentFromGid(gid)
if signature is None:
# Create a new signature when needed
# XXX what if it does not happen on a Add command ?
signature = self.newContent(
portal_type='SyncML Signature',
id=gid,
)
syncml_logger.debug("Created a signature for %s - document : %s"
% (signature.getPath(), document))
if document is not None:
signature.setReference(document.getPath())
security.declareProtected(Permissions.ModifyPortalContent,
'initLastMessageId')
def initLastMessageId(self, last_message_id=0):
elif signature.getValidationState() == 'synchronized':
# Reset status of signature synchronization
signature.drift()
force = signature.isForce() # XXX-must check the use of this later
else:
force = True # Always erease data in this mode
document = None # For now, do no try to retrieve previous version of document
# XXX this has to be managed with a property
# XXX Some improvement can also be done to retrieve a list of document at once
# Get the data
if 'xml_data' in action:
# Rebuild an Element
incoming_data = etree.fromstring(action["xml_data"])
else: # Raw data
incoming_data = action['raw_data']
# XXX must find a way to check for No data received here
if not action['more_data']:
# This is the last chunk of a partial xml
# or this is just an entire data chunk
if signature and signature.hasPartialData():
# Build data with already stored data
signature.appendPartialData(incoming_data)
incoming_data = signature.getPartialData()
signature.setPartialData(None)
# Browse possible actions
if action["command"] == 'Add':
status_code = "item_added" # Default status code for addition
if document is None:
# This is the default behaviour when getting an "Add" command
# we create new document from the received data
syncml_logger.debug("Calling addNode with no previous document found")
add_data = conduit.addNode(xml=incoming_data,
object=destination,
signature=signature,
domain=self)
conflict_list.extend(add_data['conflict_list'])
# Retrieve directly the document from addNode
document = add_data['object']
if document is None:
raise ValueError("Adding a document failed, data = %s"
% (etree.tostring(incoming_data,
pretty_print=True),))
else:
# Document was retrieved from the database
actual_xml = conduit.getXMLFromObjectWithGid(document, gid,
xml_mapping=\
self.getXmlBindingGeneratorMethodId(force=True),
context_document=self.getPath())
# use gid to compare because their ids can be different
incoming_data = conduit.replaceIdFromXML(incoming_data, 'gid', gid)
# produce xupdate
data_diff = conduit.generateDiff(new_data=incoming_data,
former_data=actual_xml)
if data_diff and len(data_diff):
# XXX Here maybe a conflict must be raised as document was never
# synchronized and we try to add one which is different
syncml_logger.critical("trying to add data, but already existing object exists, diff is\n%s" % (data_diff))
conflict_list.extend(conduit.updateNode(
xml=data_diff,
object=document,
previous_xml=actual_xml,
force=force,
simulate=simulate,
reset=True,
signature=signature,
domain=self))
xml_document = incoming_data
if not isinstance(xml_document, basestring):
# XXX using deepcopy to remove parent link - must be done elsewhere
xml_document = deepcopy(xml_document)
# Remove useless namespace
etree.cleanup_namespaces(xml_document)
xml_document = etree.tostring(xml_document, encoding='utf-8',
pretty_print=True)
if isinstance(xml_document, unicode):
xml_document = xml_document.encode('utf-8')
# Link the signature to the document
if signature:
signature.setReference(document.getPath())
elif action["command"] == 'Replace':
status_code = "success" # Default status code for addition
if document is not None:
signature = self.getSignatureFromGid(gid)
previous_xml = signature.getData()
if previous_xml:
# Make xml consistent XXX should be part of the conduit work
# XXX this should not happen if we call replaceIdFromXML when
# editing signature
previous_xml = conduit.replaceIdFromXML(previous_xml, 'gid', gid)
conflict_list += conduit.updateNode(xml=incoming_data,
object=document,
previous_xml=previous_xml,
force=force,
signature=signature,
simulate=False, #simulate,
domain=self)
if previous_xml:
# here compute patched data with given diff
xml_document = conduit.applyDiff(previous_xml, incoming_data)
xml_document = conduit.replaceIdFromXML(xml_document, 'id',
document.getId(),
as_string=True)
else:
raise ValueError("Got a signature with no data for %s" % (gid,))
else:
# Try to apply an update on a delete document
# What to do ?
raise ValueError("No document found to apply update")
elif action['command'] == 'Delete':
status_code="success"
document = self.getDocumentFromGid(signature.getId())
if document is not None:
# XXX Can't we get conflict ?
conduit.deleteNode(xml=incoming_data,
object=destination,
object_id=document.getId())
# Delete signature
self._delObject(gid)
else:
syncml_logger.error("Document with gid is already deleted"
% (gid,))
else:
raise ValueError("Unknown command %s" %(action['command'],))
# Now update signature status regarding conflict list
if action['command'] != "Delete" and signature:
if len(conflict_list):
status_code="conflict"
signature.changeToConflict()
# Register the data received which generated the diff
# XXX Why ?
if not isinstance(incoming_data, basestring):
incoming_data = etree.tostring(incoming_data,
encoding='utf-8')
signature.setPartialData(incoming_data)
else:
signature.setData(str(xml_document))
signature.synchronize()
syncml_logger.debug("change state of signature to %s"
% (signature.getValidationState(),))
if signature:
# Generate status about the object synchronized
# No need to generate confirmation when no signature are stored
syncml_response.addConfirmationMessage(
command=action['command'],
sync_code=status_code,
target_ref=action["target"],
source_ref=action["source"],
command_ref=action["command_id"],
message_ref=request_message_id)
else: # We want to retrieve more data
syncml_logger.debug("we need to retrieve more data for %s" % (signature,))
if signature.getValidationState() != 'partial':
signature.changeToPartial()
signature.appendPartialData(incoming_data)
# XXX Must check if size is present into the xml
# if not, client might ask it to server with a 411 alert
# in this case, do not process received data
syncml_response.addConfirmationMessage(
command=action['command'],
sync_code='chunk_accepted',
target_ref=action["target"],
source_ref=action["source"],
command_ref=action["command_id"],
message_ref=request_message_id)
# Must add an alert message to ask remaining data to be processed
# Alert 222 must be generated
# XXX Will be into the Sync tag -> bad
syncml_response.addAlertCommand(
alert_code='next_message',
target=self.getDestinationReference(),
source=self.getSourceReference(),
last_anchor=self.getLastAnchor(),
next_anchor=self.getNextAnchor())
security.declarePrivate('applyActionList')
def applyActionList(self, syncml_request, syncml_response, simulate=False):
"""
Browse the list of sync command received, apply them and generate answer
"""
for action in syncml_request.sync_command_list:
self.applySyncCommand(
action=action,
request_message_id=syncml_request.header["message_id"],
syncml_response=syncml_response,
simulate=simulate)
def _getDeletedData(self, syncml_response=None):
"""
Add delete command to syncml resposne
"""
if self.getIsActivityEnabled():
self.recurseCallMethod(
method_id="getId",
min_depth=1,
max_depth=1,
activate_kw={'priority': ACTIVITY_PRIORITY,
'group_method_id' : "%s/checkAndSendDeleteMessage"
% (self.getRelativeUrl()),
'tag' : "%s_delete" % self.getRelativeUrl()})
self.activate(after_tag="%s_delete" %(self.getRelativeUrl()),
priority=ACTIVITY_PRIORITY+1,
)._sendFinalMessage()
else:
# XXX not efficient at all but must not be used (former way)
syncml_logger.warning("Using non-efficient way to retrieve delete object on %s"
% (self.getRelativeUrl(),))
id_list = [x.getId() for x in self.contentValues() if \
x.getValidationState() == "not_synchronized"]
for gid in id_list:
syncml_response.addDeleteCommand(gid=gid)
def _sendFinalMessage(self):
"""
Send an empty message containing the final tag to notify the end of
the "sending_modification" stage of the synchronization
"""
syncml_response = SyncMLResponse()
syncml_response.addHeader(
session_id=self.getSessionId(),
message_id=self.getNextMessageId(),
target=self.getUrlString(),
source=self.getSubscriptionUrlString())
syncml_response.addBody()
syncml_response.addFinal()
final_activate_kw = {
'after_method_id' : ("processServerSynchronization",
"processClientSynchronization"),
'priority' :ACTIVITY_PRIORITY + 1,
'tag' : "%s_delete" %(self.getRelativeUrl(),)
}
syncml_logger.warning("Sending final message for modificationson on %s"
% (self.getRelativeUrl(),))
self.activate(**final_activate_kw).sendMessage(xml=str(syncml_response))
def checkAndSendDeleteMessage(self, message_list):
"""
This is a group method that will be invoked for a message list
It check signature synchronization state to know which one has
to be deleted and send the syncml message
"""
syncml_logger.warning("Checking deleted signature on %s"
% (self.getRelativeUrl(),))
to_delete_id_list = []
for m in message_list:
if m[0].getValidationState() == "not_synchronized":
to_delete_id_list.append(m[0].getId())
syncml_logger.warning("\tdeleted object is %s"
% (to_delete_id_list,))
if len(to_delete_id_list):
syncml_response = SyncMLResponse()
syncml_response.addHeader(
session_id=self.getSessionId(),
message_id=self.getNextMessageId(),
target=self.getUrlString(),
source=self.getSubscriptionUrlString())
syncml_response.addBody()
for gid in to_delete_id_list:
syncml_response.addDeleteCommand(gid=gid)
syncml_logger.info("%s sendDeleteCommand for %s"
% (self.getRelativeUrl(), to_delete_id_list))
self.activate(activity="SQLQueue",
tag="%s_delete" % (self.getRelativeUrl(),),
priority=ACTIVITY_PRIORITY).sendMessage(xml=str(syncml_response))
def _getSyncMLData(self, syncml_response, id_list=None):
"""
set the last message id to 0
XXX Comment to be fixed
"""
self.setLastMessageId(last_message_id)
if not id_list:
syncml_logger.warning("Non optimal call to _getSyncMLData, no id list provided : %r" %(id_list))
else:
syncml_logger.info("getSyncMLData, id list provided %s" % (id_list,))
conduit = self.getConduit()
finished = True
if isinstance(conduit, basestring):
conduit = getConduitByName(conduit)
try:
object_list = self.getDocumentList(id_list=id_list)
except TypeError:
# Old style script
warn("Script %s does not accept id_list paramater" %
(self.getListMethodId(),), DeprecationWarning)
object_list = self.getDocumentList()
loop = 0
traverse = self.getPortalObject().restrictedTraverse
alert_code = self.getSyncmlAlertCode()
sync_all = alert_code in ("refresh_from_client_only", "slow_sync")
# XXX Quick & dirty hack to prevent signature creation, this must be defined
# on pub/sub instead
create_signature = alert_code != "refresh_from_client_only"
if not len(object_list) and id_list:
syncml_logger.warning("No object retrieved althoud id_list (%s) is provided"
% (id_list))
for result in object_list:
object_path = result.getPath()
# if loop >= max_range:
# # For now, maximum object list is always none, so we will never come here !
# syncml_logger.warning("...Send too many objects, will split message...")
# finished = False
# break
# Get the GID
document = traverse(object_path)
gid = self.getGidFromObject(document)
if not gid:
raise ValueError("Impossible to compute gid for %s" %(object_path))
if True: # not loop: # or len(syncml_response) < MAX_LEN:
# XXX must find a better way to prevent sending
# no object due to a too small limit
signature = self.getSignatureFromGid(gid)
more_data = False
# For the case it was never synchronized, we have to send everything
if not signature or sync_all:
# First time we send this object or the synchronization more required
# to send every data as it was never synchronized before
document_data = conduit.getXMLFromObjectWithId(
# XXX To be renamed (getDocumentData) independant from format
document,
xml_mapping=self.getXmlBindingGeneratorMethodId(),
context_document=self.getPath())
if not document_data:
continue
if create_signature:
if not signature:
signature = self.newContent(portal_type='SyncML Signature',
id=gid,
reference=document.getPath(),
temporary_data=document_data)
syncml_logger.debug("Created a signature %s for gid = %s, path %s"
% (signature.getPath(), gid, document.getPath()))
if len(document_data) > MAX_LEN:
syncml_logger.debug("data too big, sending multiple message")
more_data = True
finished = False
document_data, rest_string = cutXML(document_data, MAX_LEN)
# Store the remaining data to send it later
signature.setPartialData(rest_string)
signature.setPartialAction(ADD_ACTION)
signature.changeToPartial()
else:
# The data will be copied in 'data' property once we get
# confirmation that the document was well synchronized
signature.setTemporaryData(document_data)
signature.doSync()
syncml_logger.debug("signature %s is syncing"
% (signature.getRelativeUrl(),))
# Generate the message
syncml_response.addSyncCommand(
sync_command=ADD_ACTION,
gid=gid,
data=document_data,
more_data=more_data,
media_type=conduit.getContentType())
elif signature.getValidationState() in ('not_synchronized',
'conflict_resolved_with_merge'):
# We don't have synchronized this object yet but it has a signature
xml_object = conduit.getXMLFromObjectWithId(document,
xml_mapping=self.getXmlBindingGeneratorMethodId(),
context_document=self.getPath())
if signature.getValidationState() == 'conflict_resolved_with_merge':
# XXX Why putting confirmation message here
# Server can get confirmation of sync although it has not yet
# send its data modification to the client
# This must be checked against specifications
syncml_response.addConfirmationMessage(
source_ref=signature.getId(),
sync_code='conflict_resolved_with_merge',
command='Replace')
if not signature.checkMD5(xml_object):
# MD5 checksum tell there is a modification of the object
if conduit.getContentType() != 'text/xml':
# If there is no xml, we re-send the whole object
# XXX this must be managed by conduit ?
data_diff = xml_object
else:
# Compute the diff
new_document = conduit.replaceIdFromXML(xml_object, 'gid', gid)
previous_document = conduit.replaceIdFromXML(signature.getData(),
'gid', gid)
data_diff = conduit.generateDiff(new_data=new_document,
former_data=previous_document)
if not data_diff:
# MD5 Checksum can detect changes like <lang/> != <lang></lang>
# but Diff generator will return no diff for it
# in this case, no need to send diff
signature.synchronize()
syncml_logger.debug("signature %s is synchronized"
% (signature.getRelativeUrl(),))
continue
# Split data if necessary
if len(data_diff) > MAX_LEN:
syncml_logger.debug("data too big, sending multiple messages")
more_data = True
finished = False
data_diff, rest_string = cutXML(data_diff, MAX_LEN)
signature.setPartialData(rest_string)
signature.setPartialAction(REPLACE_ACTION)
if signature.getValidationState() != 'partial':
signature.changeToPartial()
syncml_logger.debug("signature %s is partial"
% (signature.getRelativeUrl(),))
else:
# Store the new representation of the document
# It will be copy to "data" property once synchronization
# is confirmed
signature.setTemporaryData(xml_object)
signature.doSync()
syncml_logger.debug("signature %s is syncing"
% (signature.getRelativeUrl(),))
# Generate the command
syncml_logger.debug("will send Replace command with %s"
% (data_diff,))
syncml_response.addSyncCommand(
sync_command=REPLACE_ACTION,
gid=gid,
data=data_diff,
more_data=more_data,
media_type=conduit.getContentType())
elif signature.getValidationState() != 'synchronized':
# We should not have this case when we are in CONFLICT_MERGE
syncml_logger.debug("signature %s is synchronized"
% (signature.getRelativeUrl(),))
signature.synchronize()
elif signature.getValidationState() == \
'conflict_resolved_with_client_command_winning':
# 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.getPartialData()
previous_xml_with_gid = conduit.replaceIdFromXML(signature.getData(),
'gid', gid,
as_string=False)
conduit.updateNode(xml=xml_update, object=document,
previous_xml=previous_xml_with_gid, force=True,
gid=gid,
signature=signature,
domain=self)
syncml_response.addConfirmationMessage(
target_ref=gid,
sync_code='conflict_resolved_with_client_command_winning',
command='Replace')
signature.synchronize()
syncml_logger.debug("signature %s is synchronized"
% (signature.getRelativeUrl(),))
elif signature.getValidationState() == 'partial':
# Case of partially sent data
xml_string = signature.getPartialData()
# XXX Cutting must be managed by conduit
# Here it is too specific to XML data
if len(xml_string) > MAX_LEN:
syncml_logger.info("Remaining data too big, splitting it...")
more_data = True
finished = False
xml_string = signature.getFirstPdataChunk(MAX_LEN)
xml_string = etree.CDATA(xml_string.decode('utf-8'))
syncml_response.addSyncCommand(
sync_command=signature.getPartialAction(),
gid=gid,
data=xml_string,
more_data=more_data,
media_type=self.getContentType())
if not more_data:
signature.doSync()
syncml_logger.debug("signature %s is syncing"
% (signature.getRelativeUrl(),))
elif signature.getValidationState() in ('syncing', 'synchronized'):
raise ValueError("Must not get signature in %s state here, signature is %s"
% (signature.getValidationState(),
signature.getPath(),))
if not more_data:
pass
else:
syncml_logger.info("Splitting document")
break
else:
syncml_logger.warning("Package is going to be splitted")
break
loop += 1
syncml_logger.debug("_getSyncMLData end with finished %s"
% (finished,))
return finished
security.declareProtected(Permissions.AccessContentsInformation,
'getConduit')
def getConduit(self):
"""
Return the conduit object defined
"""
conduit_name = self.getConduitModuleId()
return getConduitByName(conduit_name)
security.declarePrivate('checkCorrectRemoteMessageId')
def checkCorrectRemoteMessageId(self, message_id):
"""
Check this is not an already processed message based on its id
If it is, the response will be resent as we do not want to reprocess
the same data again XXX Maybe it is possible to be stateless ?
Use memcache to retrieve the message so that it does not impact scalability
"""
# XXX To be done
return True
security.declareProtected(Permissions.AccessContentsInformation,
'getXmlBindingGeneratorMethodId')
def getXmlBindingGeneratorMethodId(self, default=_MARKER, force=False):
"""
return the xml mapping
XXX force parameter must be removed
Return the xml mapping
"""
if self.isOneWayFromServer() and not force:
return None
if default is _MARKER:
return self._baseGetXmlBindingGeneratorMethodId()
else:
......@@ -177,16 +831,13 @@ class SyncMLSubscription(XMLObject):
"""
Returns the object gid
"""
o_base = aq_base(object)
gid = None
# first try with new method
gid_generator = self.getGidGeneratorMethodId("")
if gid_generator not in ("", None) and getattr(self, gid_generator, None):
if gid_generator and getattr(self, gid_generator, None):
raw_gid = getattr(self, gid_generator)(object)
else:
# old way using the conduit
conduit_name = self.getConduitModuleId()
conduit = getConduitByName(conduit_name)
conduit = self.getConduit()
raw_gid = conduit.getGidFromObject(object)
if isinstance(raw_gid, unicode):
raw_gid = raw_gid.encode('ascii', 'ignore')
......@@ -197,130 +848,94 @@ class SyncMLSubscription(XMLObject):
return gid
security.declareProtected(Permissions.AccessContentsInformation,
'getObjectFromGid')
def getObjectFromGid(self, gid, use_list_method=True):
'getDocumentFromGid')
def getDocumentFromGid(self, gid):
"""
This tries to get the object with the given gid
This uses the query if it exist and use_list_method is True
Return the document for a given GID
- First try using the signature which is linked to the document
- Otherwise use the list method
"""
if len(gid)%2 != 0:
#something encode in base 16 is always a even number of number
#if not, b16decode will failed
# something encode in base 16 is always a even number of number
# if not, b16decode will failed
return None
signature = self.getSignatureFromGid(gid)
# First look if we do already have the mapping between
# the id and the gid
destination = self.getSourceValue()
if signature is not None and signature.getReference():
document_path = signature.getReference()
document = self.getPortalObject().unrestrictedTraverse(document_path, None)
if document is not None:
if signature and signature.getReference():
document = self.getPortalObject().unrestrictedTraverse(
signature.getReference(), None)
if document:
return document
#LOG('entering in the slow loop of getObjectFromGid !!!', WARNING,
#self.getPath())
if use_list_method:
object_list = self.getObjectList(gid=b16decode(gid))
object_list = self.getDocumentList(gid=b16decode(gid))
for document in object_list:
document_gid = self.getGidFromObject(document)
if document_gid == gid:
return document
#LOG('getObjectFromGid', DEBUG, 'returning None')
return None
security.declareProtected(Permissions.AccessContentsInformation,
'getObjectFromId')
def getObjectFromId(self, id):
'getDocumentIdList')
def getDocumentIdList(self, limit, **search_kw):
"""
return the object corresponding to the id
Method called to return the id list sorted within the given limits
"""
object_list = self.getObjectList(id=id)
o = None
for object in object_list:
if object.getId() == id:
o = object
break
return o
return self.getDocumentList(id_only=True, limit=limit, **search_kw)
security.declareProtected(Permissions.AccessContentsInformation,
'getObjectList')
def getObjectList(self, **kw):
'getDocumentList')
def getDocumentList(self, **kw):
"""
This returns the list of sub-object corresponding
to the query
"""
folder = self.getSourceValue()
list_method_id = self.getListMethodId()
if list_method_id is not None and isinstance(list_method_id, str):
result_list = []
if list_method_id and isinstance(list_method_id, str):
query_method = folder.unrestrictedTraverse(list_method_id, None)
if query_method is not None:
if query_method:
try:
result_list = query_method(context_document=self, **kw)
except TypeError:
result_list = query_method(**kw)
else:
raise KeyError, 'This Subscriber %s provide no list method:%r'\
% (self.getPath(), list_method_id)
else:
raise KeyError, 'This Subscriber %s provide no list method with id:%r'\
% (self.getPath(), list_method_id)
# XXX Access all objects is very costly
return [x for x in result_list
if not getattr(x, '_conflict_resolution', False)]
security.declarePrivate('generateNewIdWithGenerator')
def generateNewIdWithGenerator(self, object=None, gid=None):
"""
This tries to generate a new Id
"""
warn('Only container is allowed to compute new ID', DeprecationWarning)
id_generator = self.getSynchronizationIdGeneratorMethodId()
if id_generator is not None:
o_base = aq_base(object)
new_id = None
if callable(id_generator):
new_id = id_generator(object, gid=gid)
elif getattr(o_base, id_generator, None) is not None:
generator = getattr(object, id_generator)
new_id = generator()
else:
# This is probably a python script
generator = getattr(object, id_generator)
new_id = generator(object=object, gid=gid)
#LOG('generateNewIdWithGenerator, new_id: ', DEBUG, new_id)
return new_id
return None
return result_list
security.declareProtected(Permissions.ModifyPortalContent,
'incrementSessionId')
def incrementSessionId(self):
security.declareProtected(Permissions.ModifyPortalContent, 'generateNewSessionId')
def generateNewSessionId(self):
"""
increment and return the session id
Generate new session using portal ids
"""
session_id = self.getSessionId()
session_id += 1
self._setSessionId(session_id)
self.resetMessageId() # for a new session, the message Id must be reset
return session_id
id_group = ("session_id", self.getRelativeUrl())
return self.getPortalObject().portal_ids.generateNewId(
id_group=id_group,
id_generator="mysql_non_continuous_increasing_non_zodb",
default=1)
security.declareProtected(Permissions.ModifyPortalContent,
'incrementMessageId')
def incrementMessageId(self):
security.declareProtected(Permissions.ModifyPortalContent, 'getNextMessageId')
def getNextMessageId(self):
"""
return the message id
Generate new message id using portal ids
This depends on the session id as there is no way to reset it
"""
message_id = self.getMessageId(0)
message_id += 1
self._setMessageId(message_id)
return message_id
return self.getNextMessageIdList(id_count=1)[0]
security.declareProtected(Permissions.ModifyPortalContent,
'resetMessageId')
def resetMessageId(self):
security.declareProtected(Permissions.ModifyPortalContent, 'getNextMessageIdList')
def getNextMessageIdList(self, id_count):
"""
set the message id to 0
Generate new message id list using portal ids
This depends on the session id as there is no way to reset it
"""
self._setMessageId(0)
id_group = ("message_id", self.getRelativeUrl(), self.getSessionId())
return self.getPortalObject().portal_ids.generateNewIdList(
id_generator="mysql_non_continuous_increasing_non_zodb",
id_group=id_group, id_count=id_count, default=1)
security.declareProtected(Permissions.ModifyPortalContent,
'createNewAnchor')
......@@ -329,7 +944,7 @@ class SyncMLSubscription(XMLObject):
set a new anchor
"""
self.setLastAnchor(self.getNextAnchor())
self.setNextAnchor(DateTime())
self.setNextAnchor(buildAnchorFromDate(DateTime()))
security.declareProtected(Permissions.ModifyPortalContent,
'resetAnchorList')
......@@ -337,8 +952,8 @@ class SyncMLSubscription(XMLObject):
"""
reset both last and next anchors
"""
self.setLastAnchor(None)
self.setNextAnchor(None)
self.setLastAnchor(NULL_ANCHOR)
self.setNextAnchor(NULL_ANCHOR)
security.declareProtected(Permissions.AccessContentsInformation,
'getSignatureFromObjectId')
......@@ -348,15 +963,14 @@ class SyncMLSubscription(XMLObject):
### Use a reverse dictionary will be usefull
to handle changes of GIDs
"""
document = None
# XXX very slow
for signature in self.objectValues():
document = signature.getSourceValue()
if document is not None:
if id == document.getId():
document = signature
break
return document
return signature
else: # XXX-Aurel : maybe none is expected
raise KeyError, id
security.declareProtected(Permissions.AccessContentsInformation,
'getSignatureFromGid')
......@@ -366,14 +980,6 @@ class SyncMLSubscription(XMLObject):
"""
return self._getOb(gid, None)
security.declareProtected(Permissions.AccessContentsInformation,
'getGidList')
def getGidList(self):
"""
Returns the list of gids from signature
"""
return [id for id in self.getObjectIds()]
security.declareProtected(Permissions.AccessContentsInformation,
'getSignatureList')
@deprecated
......@@ -396,9 +1002,12 @@ class SyncMLSubscription(XMLObject):
'resetSignatureList')
def resetSignatureList(self):
"""
Reset all signatures in activities
XXX Method must be renamed as it delete signature and do no
reset them
Delete signature in acticities
XXX Must also be splitted in activity like the real reset
"""
object_id_list = [id for id in self.getObjectIds()]
object_id_list = list(self.getObjectIds())
object_list_len = len(object_id_list)
for i in xrange(0, object_list_len, MAX_OBJECTS):
current_id_list = object_id_list[i:i+MAX_OBJECTS]
......@@ -416,42 +1025,32 @@ class SyncMLSubscription(XMLObject):
conflict_list.extend(signature.getConflictList())
return conflict_list
security.declareProtected(Permissions.ModifyPortalContent,
'removeRemainingObjectPath')
def removeRemainingObjectPath(self, object_path):
"""
We should now wich objects should still
synchronize
"""
remaining_object_list = self.getProperty('remaining_object_path_list')
if remaining_object_list is None:
# it is important to let remaining_object_path_list to None
# it means it has not beeing initialised yet
return
new_list = []
new_list.extend(remaining_object_list)
while object_path in new_list:
new_list.remove(object_path)
self._edit(remaining_object_path_list=new_list)
security.declareProtected(Permissions.ModifyPortalContent,
'initialiseSynchronization')
def initialiseSynchronization(self):
"""
Set the status of every object as not_synchronized
XXX Improve method to not fetch many objects in unique transaction
Set the status of every signature as not_synchronized
"""
LOG('Subscription.initialiseSynchronization()', 0, self.getPath())
if self.getIsActivityEnabled():
self.getAndActivateResetSignature()
else:
for signature in self.contentValues(portal_type='SyncML Signature'):
# Change the status only if we are not in a conflict mode
if signature.getValidationState() not in ('conflict',
if signature.getValidationState() not in (
'conflict',
'conflict_resolved_with_merge',
'conflict_resolved_with_client_command_winning'):
if self.getIsActivityEnabled():
signature.activate(activity='SQLQueue',
priority=ACTIVITY_PRIORITY).reset()
else:
signature.reset()
self._edit(remaining_object_path_list=None)
security.declareProtected(Permissions.ModifyPortalContent,
'getAndActivateResetSignature')
def getAndActivateResetSignature(self, min_packet_id=0):
"""
Reset signature by packet (i.e. getAndActivate)
"""
self.recurseCallMethod(method_id="reset",
method_kw={"no_conflict": True},
min_depth=1,
max_depth=1,
activate_kw={'priority': ACTIVITY_PRIORITY,
'tag' : "%s_reset" % self.getPath()})
#!/usr/bin/python
# coding=UTF-8
import httplib
import urllib,urllib2, os
import cStringIO
import string
import urllib,urllib2
import socket
import time
from optparse import OptionParser
......
# -*- coding: utf-8 -*-
## Copyright (c) 2012 Nexedi SARL and Contributors. All Rights Reserved.
# Aurélien Calonne <aurel@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.
#
##############################################################################
from logging import getLogger
from Products.ERP5SyncML.Engine.EngineMixin import EngineMixin
from Products.ERP5SyncML.SyncMLConstant import ACTIVITY_PRIORITY
from Products.ERP5.ERP5Site import getSite
syncml_logger = getLogger('ERP5SyncML')
class SyncMLAsynchronousEngine(EngineMixin):
""" Implement synchronization engine using activities """
def processClientSynchronization(self, syncml_request, subscription):
""" Global method that process the package 3, 4 & 5 of SyncML DS Protocol """
syncml_logger.info("xxx Client processing data from server (%s) xxx"
% (subscription.getSynchronizationState()))
syncml_logger.info("\tstatus %s, sync %s, final %s"
% (len(syncml_request.status_list),
len(syncml_request.sync_command_list),
syncml_request.isFinal))
# We must log in with user defined
subscription._loginUser()
if syncml_request.alert_list:
syncml_logger.warning("Got an alert from server not processed : %s"
% (syncml_request.alert_list,))
# Must check what server tell about database synchronization
# and update the mode if required
# Read status about databases & synchronizations
self._readStatusList(syncml_request, subscription)
if syncml_request.isFinal and \
subscription.getSynchronizationState() == "initializing":
# Server validated authentication ( must be done in readStatus list)
# Client sends its modifications first before getting the one from server
subscription.sendModifications() # Worfklow action
syncml_response = None
tag = subscription_path = subscription.getRelativeUrl()
# Do action according to synchronization state
if subscription.getSynchronizationState() == "initializing":
raise ValueError("Subscription still initializing, must not get here")
elif subscription.getSynchronizationState() == "sending_modifications":
# This is the package 3 of the sync process
if subscription.getSyncmlAlertCode() in ("one_way_from_server",
"refresh_from_server_only"):
# We only get data from server
syncml_response = self._generateBaseResponse(subscription)
syncml_response.addFinal()
else:
self.runGetAndActivate(subscription=subscription, tag=tag)
syncml_logger.info("X-> Client is sendind modification in activities")
# As we generated all activities to send data at once, process must not
# go back here, go into processing state thus status will be applied and
# if no sync command received, the process will just go on
subscription.processSyncRequest()
elif subscription.getSynchronizationState() == "processing_sync_requests":
# In a second time, clients applied modifications from server
# This is the package 5 of the protocol
if syncml_request.isFinal or len(syncml_request.sync_command_list):
# Either we get no sync command but a final tag notifying it is finished
# Either we get sync command
# Either we get both
# Otherwise, it is just status message, no need to come here
self.runApplySyncCommand(subscription=subscription,
syncml_request=syncml_request,
tag=tag)
syncml_logger.info("-> Client apply command in %d activities"
% (len(syncml_request.sync_command_list)))
if syncml_request.isFinal:
if not syncml_response:
syncml_response = self._generateBaseResponse(subscription)
# We got and process all sync command from server
# notify it that all modifications were applied
syncml_response.addFinal()
# Send the message in activity after all sync command are applied
subscription.activate(activity="SQLQueue",
priority=ACTIVITY_PRIORITY,
after_path_and_method_id =
(subscription.getPath(),
'applySyncCommand'),
after_tag=tag,).sendMessage(
xml=str(syncml_response))
# Synchronization process is now finished
syncml_logger.info("\tClient finished processing messages from server")
subscription.finish()
syncml_response = None # XXX Do not resend the message
else:
raise ValueError("Unmanaged state of synchronization %s : %s"
% (subscription.getRelativeUrl(),
subscription.getSynchronizationState()))
if subscription.getSynchronizationState() == "finished":
# We do not expect anymore message from server
# XXX Map of UID is not implemented
syncml_logger.info('--- synchronization ended on the client side ---')
# Remove authentication
if subscription.getAuthenticationState() == 'logged_in':
subscription.logout()
subscription._edit(authenticated_user=None)
if syncml_response:
# Send the message in activity to prevent recomputing data in case of
# transport failure
syncml_logger.info("....client sending message....")
subscription.activate(activity="SQLQueue").sendMessage(
xml=str(syncml_response))
def processServerSynchronization(self, subscriber, syncml_request):
"""
Process the package 4 of the SyncML DS exchange
"""
if False:
pass
# not subscriber.checkCorrectRemoteMessageId(
# syncml_request.header['message_id']):
# # Use memcached instead of storing data on subscription
# syncml_logger.warning("Resending last message")
# syncml_response = subscriber.getLastSentMessage("") # XXX
else:
syncml_logger.info("xxx Server processing data from client xxx")
syncml_logger.info("\tstatus %s, sync %s, final %s"
% (len(syncml_request.status_list),
len(syncml_request.sync_command_list),
syncml_request.isFinal))
# we log the user authenticated to do the synchronization with him
if subscriber.getAuthenticationState() == 'logged_in':
subscriber._loginUser()
else:
# Do not run sync if not authenticated
raise ValueError("Authentication failed, impossible to sync data")
# Apply command & send modifications
# Apply status about object send & synchronized if any
sync_status_counter = self._readStatusList(syncml_request, subscriber,
generate_alert=True)
syncml_response = None
tag = subscription_path = subscriber.getRelativeUrl()
after_method_id = None
if subscriber.getSynchronizationState() == "sending_modifications":
if syncml_request.isFinal:
# Do the final process after all other message are processed
syncml_logger.info("server will finish sync in activity")
subscriber.activate(after_method_id=('processServerSynchronization',
'processClientSynchronization',
'applySyncCommand'),
after_tag=(tag,
subscriber.getParentValue().getRelativeUrl()),
activity="SQLQueue",
priority=ACTIVITY_PRIORITY+1).finishSynchronization()
# We only got notifications, nothing to do
if not len(syncml_request.sync_command_list):
return
# But we still might get sync command from activities
# Order is not yet respected for the final sync command XXX to be reviewed
# XXX We compute gid list so that we do not get issue with catalog
# XXX This is a hack, if real datasynchronization is implemented
# diff of objects must be computed before any process of data from
# clients, which is not the case here
# XXX To avoid issue with multiple message, this must be stored
# in memcached instead of this variable
if subscriber.getSynchronizationState() == "initializing":
raise ValueError("Subscription still initializing, must not get here")
if subscriber.getSynchronizationState() == "processing_sync_requests":
# First server process sync commands : Pkg 3 of the sync process
self.runApplySyncCommand(subscription=subscriber,
syncml_request=syncml_request, tag=tag)
syncml_logger.info("-> Server apply command in %d activities"
% (len(syncml_request.sync_command_list)))
if syncml_request.isFinal:
# Server then sends its modifications
subscriber.sendModifications()
# Now that everything is ok, init sync information
if subscriber.getSyncmlAlertCode() not in ("one_way_from_client",
"refresh_from_client_only"):
# Reset signature only if we have to check modifications on server side
subscriber.initialiseSynchronization()
# Start to send modification only once we have processed
# all message from client
after_method_id='processServerSynchronization',
tag = (tag, "%s_reset" % subscriber.getPath(),)
# Do not continue in elif, as sending modifications is done in the same
# package as sending notifications
if subscriber.getSynchronizationState() == "sending_modifications":
# In a second time, server send its modifications, package 4
if subscriber.getSyncmlAlertCode() in ("one_way_from_client",
"refresh_from_client_only"):
# We only get data from client
activity_created = False
else:
# Send all modification using activities
activity_created = self.runGetAndActivate(subscription=subscriber,
after_method_id=after_method_id,
tag=tag)
syncml_logger.info("X--> Server is sending modifications in activities %s" %(activity_created))
if not activity_created:
# Server has no modification to send to client, return final message
syncml_logger.info("X-> Server sending final message")
if not syncml_response:
syncml_response = self._generateBaseResponse(subscriber)
syncml_response.addFinal()
if subscriber.getSynchronizationState() == "finished":
raise ValueError('Should not get here')
if syncml_response:
subscriber.activate(activity="SQLQueue",
after_method_id=after_method_id,
after_tag=tag).sendMessage(
xml=str(syncml_response))
def runGetAndActivate(self, subscription, tag, after_method_id=None):
"""
Generate tag and method parameter and call the getAndActivate method
"""
activate_kw = {
'activity' : 'SQLQueue',
'after_method_id' : after_method_id,
'tag' :tag,
'priority' :ACTIVITY_PRIORITY
}
method_kw = {
'subscription_path' : subscription.getRelativeUrl(),
}
pref = getSite().portal_preferences
count = subscription.getAndActivate(
callback="sendSyncCommand",
method_kw=method_kw,
activate_kw=activate_kw,
packet_size=pref.getPreferredDocumentRetrievedPerActivityCount(),
activity_count=pref.getPreferredRetrievalActivityCount(),
)
# Then get deleted document
# this will send also the final message of this sync part
subscription.activate(after_tag=tag)._getDeletedData()
return True
def runApplySyncCommand(self, subscription, syncml_request, tag):
"""
Launch the apply sync command in activity
"""
send_response = subscription.getSyncmlAlertCode() != "refresh_from_client_only"
if send_response and len(syncml_request.sync_command_list):
# Generate a list of responses ID here to be scallable
response_id_list = subscription.getNextMessageIdList(
id_count=len(syncml_request.sync_command_list))
response_id_list.reverse()
else:
response_id_list = [None for x in
xrange(len(syncml_request.sync_command_list))]
split = getSite().portal_preferences.getPreferredSyncActionPerActivityCount()
if not split:
if send_response:
syncml_response = self._generateBaseResponse(subscription)
else:
syncml_response = None
subscription.applyActionList(syncml_request, syncml_response)
if syncml_response:
subscription.activate(
activity="SQLQueue",
priority=ACTIVITY_PRIORITY,
tag=subscription.getRelativeUrl()).sendMessage(xml=str(syncml_response))
else:
# XXX For now always split by one
activate = subscription.getPortalObject().portal_synchronizations.activate
activate_kw = {
"activity" :"SQLQueue",
"priority" : ACTIVITY_PRIORITY,
"tag" : tag,
"group_method_id" : None,
"group_method_cost" : 1./float(split),
}
for action in syncml_request.sync_command_list:
syncml_logger.info("---> launch action in activity %s" %(action,))
activate(**activate_kw).applySyncCommand(
subscription_path=subscription.getRelativeUrl(),
response_message_id=response_id_list.pop(),
activate_kw=activate_kw,
action=action,
request_message_id=syncml_request.header["message_id"],
simulate=False)
# XXX Response is not send here
# -*- coding: utf-8 -*-
## Copyright (c) 2012 Nexedi SARL and Contributors. All Rights Reserved.
# Aurélien Calonne <aurel@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.
#
##############################################################################
from logging import getLogger
from AccessControl import ClassSecurityInfo, getSecurityManager
from Products.PluggableAuthService.interfaces.plugins import \
IAuthenticationPlugin
from Products.ERP5SyncML.XMLSyncUtils import resolveSyncmlStatusCode, decode
from Products.ERP5SyncML.SyncMLMessage import SyncMLResponse
from Products.ERP5SyncML.SyncMLConstant import NULL_ANCHOR, ACTIVITY_PRIORITY, \
SynchronizationError
syncml_logger = getLogger('ERP5SyncML')
class EngineMixin(object):
"""
Mixin class that holds generci methods used by engines
"""
security = ClassSecurityInfo()
def _generateBaseResponse(self, subscription):
syncml_response = SyncMLResponse()
syncml_response.addHeader(
session_id=subscription.getSessionId(),
message_id=subscription.getNextMessageId(),
target=subscription.getUrlString(),
source=subscription.getSubscriptionUrlString())
syncml_response.addBody()
return syncml_response
security.declarePrivate('_readStatusList')
def _readStatusList(self, syncml_request, domain, syncml_response=None,
generate_alert=False):
"""
Read status (answer to command) and act according to them
"""
sync_status_counter = 0
for status in syncml_request.status_list:
if status["command"] == "SyncHdr": # Check for authentication
if domain.getSynchronizationState() != "initializing":
raise SynchronizationError(
"Authentication header found although it is already done")
if status['status_code'] == \
resolveSyncmlStatusCode('missing_credentials'):
# Server challenged an authentication
syncml_logger.info("\tServer required an authentication")
if domain.getAuthenticationFormat() != \
status['authentication_format'] or \
domain.getAuthenticationType() != \
status['authentication_type']:
raise ValueError('Authentication definition mismatch between \
client (%s-%s) and server (%s-%s)' % (domain.getAuthenticationFormat(),
domain.getAuthenticationType(),
status['authentication_format'],
status['authentication_type']))
# XXX Not working To Review !
raise NotImplementedError("Adding credentials")
syncml_response = self._generateBaseResponse(domain)
syncml_response.addCredentialMessage(domain)
return syncml_response
elif status['status_code'] == \
resolveSyncmlStatusCode('invalid_credentials'):
syncml_logger.error("\tClient authentication refused")
raise ValueError("Server rejected client authentication")
elif status['status_code'] == \
resolveSyncmlStatusCode('authentication_accepted'):
syncml_logger.error("\tClient authentication accepted")
else:
raise ValueError('Unknown status code %s for authentication'
% (status['status_code']))
elif status["command"] == "Alert": # Status about database synchronization
# XXX Must check status for asked synchrozation
# and must be done for command, not for
# For now do nothing = say it is always OK
syncml_logger.info("\tChecking database, will generate alert %s"
% (generate_alert))
elif status["command"] in ('Add', 'Replace'):
sync_status_counter += 1
object_gid = status['source'] or status['target']
if domain.getSyncmlAlertCode() == "refresh_from_client_only":
# No signature is created for this kind of sync
if status['status_code'] not in (resolveSyncmlStatusCode('success'),
resolveSyncmlStatusCode('item_added')):
raise ValueError("Impossible to synchronize object %s"
% (status['source'] or status['target']))
else:
signature = domain.getSignatureFromGid(object_gid)
if not signature: # XXX previous call must raise Key/Index-Error instead
raise ValueError("Impossible to find signature in %s for gid %s"
% (domain.getPath(), object_gid))
if status['status_code'] == resolveSyncmlStatusCode('conflict'):
signature.changeToConflict()
syncml_logger.error("\tObject in conflict %s" %
(status['source'] or status['target']))
elif status['status_code'] == resolveSyncmlStatusCode(
'conflict_resolved_with_merge'):
# We will have to apply the update, and we should not care
# about conflicts, so we have to force the update
signature.drift()
signature.setForce(True)
syncml_logger.error("\tObject merged %s" %
(status['source'] or status['target']))
elif status['status_code'] in (resolveSyncmlStatusCode('success'),
resolveSyncmlStatusCode('item_added'),
resolveSyncmlStatusCode(
'conflict_resolved_with_client_command_winning')):
syncml_logger.error("\tObject synchronized %s" %
(status['source'] or status['target'],))
signature.synchronize()
elif status['status_code'] == resolveSyncmlStatusCode('chunk_accepted'):
syncml_logger.info("Chunk was accepted for %s" % (object_gid,))
else:
raise ValueError("Unknown status code : %r" % (status['status_code'],))
elif status['command'] == 'Delete':
sync_status_counter += 1
object_gid = status['source'] or status['target']
signature = domain.getSignatureFromGid(object_gid)
if status['status_code'] == resolveSyncmlStatusCode('success'):
if signature:
domain._delObject(signature.getId())
else:
raise ValueError("Found no signature to delete")
else:
raise ValueError("Unknown status code : %r" % (status['status_code'],))
syncml_logger.error("\tObject deleted %s" %
(status['source'] or status['target']))
else:
raise ValueError("Unknown status command : %r" % (status['command'],))
return sync_status_counter
#
# Method used by the SyncML DS Client (ie Subscription)
#
def initializeClientSynchronization(self, subscription):
""" Client Initialisation package to server (pkg 1)
Client must inform the server which database it want to synchronize
and which type og synchronization is desired.
Options that can be included in this package :
- authentification
- service capabilities (PUT)
Note that this package can be combined with package 3, this is the case of
'Sync without separate initialization'. Client may implement it. This is not
done here but can be a way of improvement to decrease number of messages
exchanged.
"""
syncml_logger.info('--- Starting synchronization on client side : %s ---'
% (subscription.getPath(),))
if not subscription.getSynchronizationState() == "initializing":
# This can be called many time in sync init when credentials failed
subscription.initialize() # Worflow action
subscription.createNewAnchor()
# The user launching the synchronization is save so that when we get an
# request from the the server, it get process with this user
# XXX this can be managed using credentials like on server part ?
user_id = getSecurityManager().getUser().getId()
subscription._loginUser(user_id)
subscription._edit(authenticated_user=user_id)
if subscription.getAuthenticationState() != 'logged_in':
# Workflow action
subscription.login()
if subscription.getSyncmlAlertCode() not in ("one_way_from_server",
"refresh_from_server_only"):
# Reset signature only if client send its modification to server
subscription.initialiseSynchronization()
# Create the package 1
syncml_response = SyncMLResponse()
# Create the header part
session_id = subscription.generateNewSessionId()
subscription.setSessionId(session_id)
header_kw = {'session_id': session_id,
'message_id': subscription.getNextMessageId(),
'target': subscription.getUrlString(),
'source': subscription.getSubscriptionUrlString(),
# Include credentials
'user_id': subscription.getUserId(),
'password': subscription.getPassword(),
'authentication_format':
subscription.getAuthenticationFormat(),
'authentication_type':
subscription.getAuthenticationType()
}
syncml_response.addHeader(**header_kw)
# Create the body part which consists of :
# - one alert command per database to sync, each containing its anchors
# - one put command (optional)
# - one get command if client when device capabilities of server (optional)
syncml_response.addBody()
# Here we only run one synchronization at a time, so only one alert command
# is created
syncml_response.addAlertCommand(
alert_code=subscription.getSyncmlAlertCode(),
target=subscription.getDestinationReference(),
source=subscription.getSourceReference(),
last_anchor=subscription.getLastAnchor(),
next_anchor=subscription.getNextAnchor())
# Generate the put command
syncml_response.addPutMessage(subscription)
return syncml_response
#
# Methods used by the SyncML DS Server (ie Publication in erp5)
#
def processServerInitialization(self, publication, syncml_request, subscriber,
alert_dict):
"""
This is the method called on server side when initializing a
new synchronization.
This method is called by client on server. Server will generate
Package 2 messages based on what it got from clients
Server will returns :
- Header with credential if authentication is needed
- Status answering the authentication & alert commands of the client
- Alert command for each database to be synchronized
(Following messages/commands are not implemented)
- Status about the device information if sent by client
- Result element containing device information if client requested it
- Put command if the server want to send its service capabilities
- Get command if the server want to get the client service capabilities
"""
if subscriber is None:
# first synchronization, create the subscribtion object under the publication
# it will be used to store status of synchronization
syncml_logger.info("\t\tCreating a subscriber")
# Define source/destination on subscriber as it will be used by protocol
# Source is server/publication, Destination is client/subscription
subscriber = publication.createUnrestrictedSubscriber(
# Publication information
source_reference=alert_dict['target'],
subscription_url_string=syncml_request.header['target'],
# Subscription information
destination_reference=alert_dict['source'],
url_string=syncml_request.header['source'],
# Other information copied from publication
xml_binding_generator_method_id=
publication.getXmlBindingGeneratorMethodId(),
conduit_module_id=publication.getConduitModuleId(),
list_method_id=publication.getListMethodId(),
gid_generator_method_id=publication.getGidGeneratorMethodId(),
source=publication.getSource(),
synchronization_id_generator_method_id =
publication.getSynchronizationIdGeneratorMethodId(), # XXX Deprecated
is_activity_enabled = publication.getIsActivityEnabled(),
# Protocol information
syncml_alert_code="syncml_alert_code/%s" %(alert_dict["code"],),
session_id=syncml_request.header['session_id'],
last_message_id=syncml_request.header['message_id'],
)
else:
if subscriber.getSessionId() == syncml_request.header['session_id']:
# We do not start a new session migth be a duplicated message
if not subscriber.checkCorrectRemoteMessageId(
syncml_request.header["message_id"]):
syncml_logger.warning("Resending last init message")
return subscriber.getLastSentMessage("")
else:
# XXX must check that previous session ended before
subscriber.edit(session_id=syncml_request.header['session_id'])
if alert_dict["code"] in ('two_way', 'slow_sync',
'one_way_from_server',
'refresh_from_client_only',
'one_way_from_client'):
# XXX Why re-editing here ?
subscriber.setXmlBindingGeneratorMethodId(
publication.getXmlBindingGeneratorMethodId())
subscriber.setConduitModuleId(publication.getConduitModuleId())
else:
raise NotImplementedError('Alert code not handled yet: %r'
% syncml_request.alert['data'])
syncml_logger.info('--- Starting synchronization on server side : %s in mode %s ---'
% (publication.getPath(), alert_dict["data"]))
# at the begining of the synchronization the subscriber is not authenticated
if subscriber.getAuthenticationState() == 'logged_in':
subscriber.logout()
if not subscriber.getSynchronizationState() == "initializing":
# This can be called many time in sync init when credentials failed
subscriber.initialize() # Workflow action
# XXX it must be known that here we do a server layer authentication,
# The database layer authentication is not implemented, although we defined
# credentials on pub/sub documents
authentication_code = None
if not len(syncml_request.credentials):
syncml_logger.info("\tReceived message without credential, will ask for them")
authentication_code = "missing_credentials"
else:
# First try to authenticate the client
if syncml_request.credentials['type'] == "syncml:auth-md5":
# MD5 authentication is not supported
raise NotImplementedError("MD5 authentication not supported")
if syncml_request.credentials['type'] == publication.getAuthenticationType():
decoded = decode(syncml_request.credentials['format'],
syncml_request.credentials['data'])
if decoded and ':' in decoded:
login, password = decoded.split(':')
# TODO: make it work for users existing anywhere
user_folder = publication.getPortalObject().acl_users
for plugin_name, plugin in user_folder._getOb('plugins')\
.listPlugins(IAuthenticationPlugin):
if plugin.authenticateCredentials(
{'login': login, 'password': password}) is not None:
subscriber.login()
syncml_logger.info("\tServer accepted authentication for user %s"
% (login,))
authentication_code = 'authentication_accepted'
subscriber._loginUser(login)
subscriber._edit(authenticated_user=login)
break
else:
# When authentication is invalid, a second try is possible for the client
# if header_kw["message_id"] == 1:
# authentication_code = 'missing_credentials'
# else:
authentication_code = 'invalid_credentials'
syncml_logger.error("\tServer rejected authentication for %s" % (login))
else:
# if header_kw["message_id"] == 1:
# authentication_code = 'missing_credentials'
# else:
authentication_code = 'invalid_credentials'
syncml_logger.warning(
"\tCredentials does not look like auth-basis, decoded value is '%s,'"
% (decoded))
else:
# To complete, must send a challenge message
syncml_logger.warning(
"\tAuthentication type does not math, from client '%s', from server '%s'" %(
syncml_request.credentials['type'],
publication.getAuthenticationType()))
authentication_code = 'missing_credentials'
# Build the xml message for the Sync initialization package
syncml_response = SyncMLResponse()
syncml_response.addHeader(session_id=subscriber.getSessionId(),
message_id=subscriber.getNextMessageId(),
target=syncml_request.header['source'],
source=publication.getUrlString())
# syncml body
syncml_response.addBody()
if authentication_code == 'authentication_accepted':
sync_type_validation_code = "success"
sync_type = alert_dict["data"]
# Now check the alert command sent by client
if alert_dict['code'] == 'slow_sync' and \
subscriber.getNextAnchor() != NULL_ANCHOR:
# If slow sync, then resend everything
syncml_logger.info("\tClient requested a slow sync, signatures are reset on server side")
subscriber.resetAllSignatures()
subscriber.resetAnchorList()
elif subscriber.getNextAnchor() != alert_dict['last_anchor']:
# Anchor does not match, must start a slow sync
syncml_logger.warning("\tAnchor does not match on server, \
received is %s, stored %s. Will start a slow sync"
%(alert_dict['last_anchor'],
subscriber.getNextAnchor()))
sync_type_validation_code = "command_failed" # Error 500
sync_type = 'slow_sync'
else:
# Last synchronization went fine
subscriber.setNextAnchor(alert_dict['next_anchor'])
# Two status message must be sent here
# - One answering about the authentication
# - One answering about the database synchronization status
# Then one aler message per database is send, client must
# follow sync type in this alert message is status was KO
# Status about authentication
syncml_response.addStatusMessage(
message_reference=syncml_request.header['message_id'],
command_reference=0, # answer to a header
command='SyncHdr',
status_code=authentication_code,
target=syncml_request.header['target'],
source=syncml_request.header['source']
)
# Status about database sync
syncml_response.addStatusMessage(
message_reference=syncml_request.header['message_id'],
command_reference=alert_dict['command_id'],
command='Alert',
status_code=sync_type_validation_code,
target=alert_dict['target'],
source=alert_dict['source'],
anchor=alert_dict['next_anchor'])
# one alert message for each database to sync
syncml_response.addAlertCommand(
alert_code=sync_type,
target=alert_dict['target'],
source=alert_dict['source'],
last_anchor=subscriber.getLastAnchor(),
next_anchor=subscriber.getNextAnchor())
# Server get sync commands from client first
subscriber.processSyncRequest()
else:
# Add a status message with a challenge command
syncml_response.addChallengeMessage(
message_reference=syncml_request.header['message_id'],
target=syncml_request.header['source'],
source=syncml_request.header['target'],
authentication_format=publication.getAuthenticationFormat(),
authentication_type=publication.getAuthenticationType(),
authentication_code=authentication_code)
# Generate and send the message
syncml_response.addFinal()
if subscriber.getIsActivityEnabled():
subscriber.activate(
activity="SQLQueue",
after_tag = "%s_reset" % subscriber.getPath(),
# Wait for all reset to be done
# before starting sync
priority=ACTIVITY_PRIORITY,
tag=publication.getRelativeUrl()).sendMessage(xml=str(syncml_response))
else:
subscriber.sendMessage(xml=str(syncml_response))
return syncml_response
# -*- coding: utf-8 -*-
## Copyright (c) 2012 Nexedi SARL and Contributors. All Rights Reserved.
# Aurélien Calonne <aurel@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.
#
##############################################################################
from logging import getLogger
from Products.ERP5SyncML.Engine.EngineMixin import EngineMixin
from Products.ERP5SyncML.SyncMLConstant import SynchronizationError
syncml_logger = getLogger('ERP5SyncML')
class SyncMLSynchronousEngine(EngineMixin):
"""
Implement a synchronous engine wait for IO
"""
def processClientSynchronization(self, syncml_request, subscription):
""" Global method that process the package 3 of SyncML DS Protocol """
syncml_logger.info("xxx Client processing data from server xxx")
syncml_logger.info("\tstatus %s, sync %s, final %s"
% (len(syncml_request.status_list),
len(syncml_request.sync_command_list),
syncml_request.isFinal))
# Process sync logged in
subscription._loginUser()
if syncml_request.alert_list:
syncml_logger.warning("Got an alert from server not processed : %s"
% (syncml_request.alert_list,))
# Must check what server tell about database synchronization
# and update the mode if required
syncml_response = self._generateBaseResponse(subscription)
# Read & apply status about databases & synchronizations
try:
self._readStatusList(syncml_request, subscription, syncml_response)
except SynchronizationError:
# Looks like we process an already received message
syncml_logger.error("%s does no process packet due to error"
% (subscription.getRelativeUrl()))
return
if syncml_request.isFinal and \
subscription.getSynchronizationState() == "initializing":
# Client sends its modifications first
# before getting the one from server
subscription.sendModifications() # Worfklow action
# Do action according to synchronization state
if subscription.getSynchronizationState() == "initializing":
raise ValueError("Subscription still initializing, must not get here")
elif subscription.getSynchronizationState() == "sending_modifications":
# Client always sent its modifications first
if subscription.getSyncmlAlertCode() in ("one_way_from_server",
"refresh_from_server_only"):
# We only get data from server
finished = True
else:
finished = subscription._getSyncMLData(
syncml_response=syncml_response,
)
syncml_logger.info("-> Client sendind modification, finished %s" % (finished,))
if finished:
# Add deleted objets
subscription._getDeletedData(syncml_response=syncml_response)
# Notify that all modifications were sent
syncml_response.addFinal()
# Will then start processing sync commands from server
subscription.processSyncRequest()
elif subscription.getSynchronizationState() == "processing_sync_requests":
# In a second time, clients applied modifications from server
if subscription.getSyncmlAlertCode() == "refresh_from_server_only":
syncml_response=None
subscription.applyActionList(
syncml_request=syncml_request,
syncml_response=syncml_response,
simulate=False)
syncml_logger.info("-> Client sending %s notification of object synchronized"
% (syncml_response.sync_confirmation_counter))
if syncml_request.isFinal:
# Notify that all modifications were applied
syncml_response.addFinal()
# Synchronization process is now finished
subscription.finish()
else:
raise ValueError("Unmanaged state of synchronization %s : %s"
% (subscription.getRelativeUrl(),
subscription.getSynchronizationState()))
if subscription.getSynchronizationState() == "finished":
# We do not expect anymore message from server
# XXX Map of UID is not implemented
syncml_logger.info('--- synchronization ended on the client side ---')
# Remove authentication
if subscription.getAuthenticationState() == 'logged_in':
subscription.logout()
subscription._edit(authenticated_user=None)
# Send the message
subscription.sendMessage(xml=str(syncml_response))
return str(syncml_response)
def processServerSynchronization(self, subscriber, syncml_request):
"""
Process the package 4 of the SyncML DS exchange
"""
if not subscriber.checkCorrectRemoteMessageId(
syncml_request.header['message_id']):
syncml_logger.warning("Resending last message")
syncml_response = subscriber.getLastSentMessage("") # XXX
else:
syncml_logger.info("xxx Server processing data from client xxx")
syncml_logger.info("\tstatus %s, sync %s, final %s"
% (len(syncml_request.status_list),
len(syncml_request.sync_command_list),
syncml_request.isFinal))
# we log the user authenticated to do the synchronization with him
if subscriber.getAuthenticationState() == 'logged_in':
subscriber._loginUser()
else:
# Do not run sync if not authenticated
raise ValueError("Authentication failed, impossible to sync data")
# Apply command & send modifications
syncml_response = self._generateBaseResponse(subscriber)
# Apply status about object send & synchronized if any
self._readStatusList(syncml_request, subscriber, syncml_response, True)
if syncml_request.isFinal:
if subscriber.getSynchronizationState() == \
"waiting_notifications":
# We got the last notifications from clients
subscriber.finish()
elif subscriber.getSynchronizationState() != \
"processing_sync_requests":
raise SynchronizationError("Got final request although not waiting for it")
# XXX We compute gid list so that we do not get issue with catalog
# XXX This is a hack, if real datasynchronization is implemented
# diff of objects must be computed before any process of data from
# clients, which is not the case here
# XXX To avoid issue with multiple message, this must be stored
# in memcached instead of this variable
if subscriber.getSynchronizationState() == "initializing":
raise ValueError("Subscription still initializing, must not get here")
if subscriber.getSynchronizationState() == "processing_sync_requests":
# First server process sync commands from client
if subscriber.getSyncmlAlertCode() == "refresh_from_client_only":
# No need to send back hack XXX this is erp5 specific optimisation
# as no signature is created
subscriber.applyActionList(
syncml_request=syncml_request,
syncml_response=None,
simulate=True)
else:
subscriber.applyActionList(
syncml_request=syncml_request,
syncml_response=syncml_response,
simulate=True)
syncml_logger.info("-> Server sending %s notification of sync"
% (syncml_response.sync_confirmation_counter))
if syncml_request.isFinal:
# Server will now send its modifications
subscriber.sendModifications()
if subscriber.getSyncmlAlertCode() not in ("one_way_from_client",
"refresh_from_client_only"):
# Reset signature only if we have to check modifications on server side
subscriber.initialiseSynchronization()
# Do not continue in elif, as sending modifications is done in the same
# package as sending notifications
if subscriber.getSynchronizationState() == "sending_modifications":
# In a second time, server send its modifications
if subscriber.getSyncmlAlertCode() in ("one_way_from_client",
"refresh_from_client_only"):
# We only get data from client
finished = True
else:
finished = subscriber._getSyncMLData(
syncml_response=syncml_response)
syncml_logger.info("-> Server sendind data, finished %s" % (finished,))
if finished:
subscriber._getDeletedData(syncml_response=syncml_response)
syncml_response.addFinal()
subscriber.waitNotifications()
# Do not go into finished here as we must wait for
# notifications from client
if subscriber.getSynchronizationState() == "finished":
syncml_logger.info('--- synchronization ended on the server side ---')
if subscriber.getAuthenticationState() == 'logged_in':
subscriber.logout()
subscriber._edit(authenticated_user=None,
remaining_object_path_list=None)
syncml_response = "" # XXX This is expected by unit test only
# Body must be sent even when there is no data to notify client
subscriber.sendMessage(xml=str(syncml_response))
# Return message for unit test purpose
return str(syncml_response)
......@@ -27,8 +27,6 @@
#
##############################################################################
from Products.ERP5Type.Globals import Persistent
import re
# Namespaces.
SYNCML_NAMESPACE = 'SYNCML:SYNCML1.2'
......@@ -74,6 +72,7 @@ PUB_CONFLICT_CLIENT_WIN = 8
#MAX_LINES = 5000
MAX_OBJECTS = 300
MAX_LEN = 1<<16
MAX_DOCUMENT_PER_MESSAGE = 2
XUPDATE_INSERT_LIST = ('xupdate:insert-after', 'xupdate:insert-before')
XUPDATE_ADD = 'xupdate:append'
......@@ -100,3 +99,5 @@ REPLACE_ACTION = 'Replace'
ACTIVITY_PRIORITY = 5
class SynchronizationError(Exception):
pass
# -*- coding: utf-8 -*-
## Copyright (c) 2011 Nexedi SARL and Contributors. All Rights Reserved.
# Aurélien Calonne <aurel@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.
#
##############################################################################
from lxml.builder import ElementMaker
from lxml.etree import Element
from lxml import etree
from Products.ERP5SyncML.XMLSyncUtils import resolveSyncmlStatusCode, \
encode, resolveSyncmlAlertCode
from Products.ERP5SyncML.SyncMLConstant import SYNCML_NAMESPACE, NSMAP
parser = etree.XMLParser(remove_blank_text=True)
E = ElementMaker(namespace=SYNCML_NAMESPACE, nsmap=NSMAP)
# We use the version 1.2 of the protocol defined by the open mobile alliance
# See http://www.openmobilealliance.org/technical/release_program/ds_v12.aspx
DS_PROTOCOL_VERSION="1.2"
class SyncMLResponse(object):
"""
SyncMLResponse is used to build a message that will be send to a syncml
server or client.
SyncML message are xml type and so are based on the lxml to create and
render it
"""
def __init__(self):
self.data = E.SyncML()
self.data_append = self.data.append
self.body = None
self.command_id = 0
self.sync_confirmation_counter = 0
self.sync = None
def __len__(self):
# To check if it has to be done on whole message or only the body
return len(etree.tostring(self.body, encoding='utf-8',
xml_declaration=True,
pretty_print=True))
def __str__(self):
return etree.tostring(self.data, encoding='utf-8', xml_declaration=True,
pretty_print=True)
def _getNextCommandId(self):
"""
Generate the next command id and store it
"""
self.command_id += 1
return str(self.command_id)
def addFinal(self):
self.body_append(E.Final())
def addBody(self):
self.body = E.SyncBody()
self.data_append(self.body)
self.body_append = self.body.append
def addHeader(self, session_id, message_id, target, source,
target_name=None, source_name=None, user_id=None,
password=None, authentication_format='b64',
authentication_type='syncml:auth-basic'):
"""
Header defines :
- protocol version (for now only 1.2 is supported)
- source & target URI
- credentials if provided
"""
xml = (E.SyncHdr(
E.VerDTD(DS_PROTOCOL_VERSION),
E.VerProto('SyncML/%s' %(DS_PROTOCOL_VERSION,)),
E.SessionID('%s' % session_id), # Global to a sync session
E.MsgID('%s' % message_id), # identify uniquely message from a
# sync session on a site
))
# Target device and service
target_node = E.Target(E.LocURI(target))
if target_name:
target_node.append(E.LocName(target_name.decode('utf-8')))
xml.append(target_node)
# Device or service addressing
# If connected to the internet, can be url form like
# If device, can be specific to device
source_node = E.Source(E.LocURI(source))
if source_name:
source_node.append(E.LocName(source_name.decode('utf-8')))
xml.append(source_node)
# Add credential informations
if user_id and password:
if authentication_type == 'syncml:auth-basic':
# base64 formating of "userid:password"
credential = "%s:%s" % (user_id, password)
credential = encode(authentication_format, credential)
elif authentication_type == "syncml:auth-md5":
# base64 coded md5 for user "XXX", password "XXX", nonce "XXX"
raise NotImplementedError("MD5 authentication not supported")
else:
raise ValueError("Bad authentication type provided : %s" %
(authentication_type,))
xml.append(
E.Cred(
E.Meta(E.Format(authentication_format, xmlns='syncml:metinf'),
E.Type(authentication_type, xmlns='syncml:metinf'),),
E.Data(credential)
))
self.data_append(xml)
def addChallengeMessage(self, message_reference, target,
source, command="SyncHdr",
authentication_format='b64',
authentication_type='syncml:auth-basic',
authentication_code='missing_credentials'):
"""
Create a challenge message (CHAL) which is used to asked credentials to a client
"""
authentication_code = resolveSyncmlStatusCode(authentication_code)
xml = (E.Status(E.CmdID(self._getNextCommandId()),
E.MsgRef(str(message_reference)),
E.CmdRef('0'),
E.Cmd(command),
E.TargetRef(target),
E.SourceRef(source),
E.Chal(
E.Meta(
E.Format(authentication_format, xmlns='syncml:metinf'),
E.Type(authentication_type, xmlns='syncml:metinf')
)
),
E.Data(authentication_code)
))
self.body_append(xml)
def addStatusMessage(self, message_reference,
command_reference, command, target,
source, status_code, anchor=None):
"""
Build a status message
Status message are used to answer a command (credential, alert) and so
contains a reference to the original message
"""
status = E.Status(
E.CmdID(self._getNextCommandId()),
E.MsgRef(str(message_reference)),
E.CmdRef(str(command_reference)),
E.Cmd(command),
E.TargetRef(target),
E.SourceRef(source),
E.Data(resolveSyncmlStatusCode(status_code)),
)
if anchor:
item = E.Item(
E.Data(
E.Anchor(anchor, xmlns='syncml:metinf')
)
)
status.append(item)
self.body_append(status)
def addAlertCommand(self, alert_code, target, source,
last_anchor, next_anchor):
"""
Construct an Alert command
"""
alert = (E.Alert(
E.CmdID(self._getNextCommandId()),
E.Data(resolveSyncmlAlertCode(alert_code)), # alert code
E.Item(
E.Target(
# Target database
E.LocURI(target)
),
E.Source(
# Source database
E.LocURI(source)
),
E.Meta(
E.Anchor(
E.Last(last_anchor),
E.Next(next_anchor)
)
)
)
)
)
self.body_append(alert)
#
# XXX Following methods must be reviewed
#
def addCredentialMessage(self, subscription):
"""
Create a credential message (CRED) which is returned by the client after
receiving a challenge message
"""
raise NotImplementedError("To review")
# create element 'SyncML' with a default namespace
xml = E.SyncML()
# syncml header
xml.append(self.buildHeader(
session_id=subscription.incrementSessionId(),
msg_id=subscription.incrementMessageId(),
target=subscription.getUrlString(),
source=subscription.getSubscriptionUrlString(),
user_id=subscription.getUserId(),
password=subscription.getPassword(),
authentication_format=subscription.getAuthenticationFormat(),
authentication_type=subscription.getAuthenticationType()))
# Build the message body
sync_body = E.SyncBody()
# alert message
sync_body.append(self.buildAlertMessage(
command_id=self._getNextCommandId(),
alert_code=subscription.getSyncmlAlertCode(),
target=subscription.getDestinationReference(),
source=subscription.getSourceReference(),
last_anchor=subscription.getLastAnchor(),
next_anchor=subscription.getNextAnchor()))
syncml_put = self.buildPutMessage(subscription)
if syncml_put is not None:
sync_body.append(syncml_put)
sync_body.append(E.Final())
xml.append(sync_body)
xml_string = etree.tostring(xml, encoding='utf-8', xml_declaration=True,
pretty_print=True)
self.data_append(xml_string)
def addPutMessage(self,subscription, markup='Put',
cmd_ref=None, message_id=None):
"""
This returns the service capabilities
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>
Use Cases :
The client must send its capabilities at firts synchronization with a server
or when its information has changed since previous synchronization.
The client must be able to send it when requested by server
The server must be albe to sent it when requested by client
Both must be able to handle and process these informations
"""
return
# XXX-Aurel : must be reviewed according to specification
# This part can be skipped for now
conduit = subscription.getConduit()
xml = None
# The conduit defined what capabilities the service offers
if getattr(conduit, 'getCapabilitiesCTTypeList', None) and \
getattr(conduit, 'getCapabilitiesVerCTList', None) and \
getattr(conduit, 'getPreferedCapabilitieVerCT', None):
xml = Element('{%s}%s' % (SYNCML_NAMESPACE, markup))
xml.append(E.CmdID(self._getNextCommandId()))
if message_id:
xml.append(E.MsgRef('%s' % message_id))
if cmd_ref:
xml.append(E.CmdRef('%s' % cmd_ref))
xml.extend((E.Meta(E.Type('application/vnd.syncml-devinf+xml')),
E.Item(E.Source(E.LocURI('./devinf12')),
E.Data(E.DevInf(E.VerDTD('1.2'),
E.Man('Nexedi'),
E.Mod('ERP5SyncML'),
E.OEM('Open Source'),
E.SwV('0.1'),
E.DevID(subscription.getSubscriptionUrlString()),
E.DevTyp('workstation'),
E.UTC(),
E.DataStore(E.SourceRef(subscription.getSourceReference()))
)
)
)))
data_store = xml.find('{%(ns)s}Item/{%(ns)s}Data/{%(ns)s}DevInf/{%(ns)s}DataStore' % {'ns': SYNCML_NAMESPACE})
tx_element_list = []
rx_element_list = []
for cttype in conduit.getCapabilitiesCTTypeList():
if cttype != 'text/xml':
for x_version in conduit.getCapabilitiesVerCTList(cttype):
rx_element_list.append(E.Rx(E.CTType(cttype), E.VerCT(x_version)))
tx_element_list.append(E.Tx(E.CTType(cttype), E.VerCT(x_version)))
rx_pref = Element('{%s}Rx-Pref' % SYNCML_NAMESPACE)
rx_pref.extend((E.CTType(conduit.getPreferedCapabilitieCTType()),
E.VerCT(conduit.getPreferedCapabilitieVerCT())))
data_store.append(rx_pref)
data_store.extend(rx_element_list)
tx_pref = Element('{%s}Tx-Pref' % SYNCML_NAMESPACE)
tx_pref.extend((E.CTType(conduit.getPreferedCapabilitieCTType()),
E.VerCT(conduit.getPreferedCapabilitieVerCT())))
data_store.append(tx_pref)
data_store.extend(tx_element_list)
data_store.append(E.SyncCap(
E.SyncType('2'),
E.SyncType('1'),
E.SyncType('4'),
E.SyncType('6')
))
self.data_append(xml)
def addSyncCommand(self, sync_command, gid, data, media_type, more_data):
"""
Generate the sync command
XXX media type must be managed by conduit, no this class
"""
self._initSyncTag()
data_node = E.Data()
# XXX to be remove later to use only CDATA
if media_type == 'text/xml':
if isinstance(data, basestring):
data_node.append(etree.XML(data, parser=parser))
elif isinstance(data, etree.CDATA):
# data could be Data element if partial XML
data_node.text = data
else:
# XXX Is it suppose to happen ?
data_node.append(data)
else:
if isinstance(data, etree.CDATA):
data_node.text = data
else:
cdata = etree.CDATA(data.decode('utf-8'))
data_node.text = cdata
main_tag = Element('{%s}%s' % (SYNCML_NAMESPACE, sync_command))
main_tag.extend((E.CmdID(self._getNextCommandId()),
E.Meta(E.Type(media_type)),
E.Item(E.Source(E.LocURI(gid)), data_node)))
if more_data:
item_node = main_tag.find('{%s}Item' % SYNCML_NAMESPACE)
item_node.append(E.MoreData())
self.sync_append(main_tag)
def _initSyncTag(self):
if self.sync is None:
# Initialize Sync subtag
self.sync = E.Sync()
self.body_append(self.sync)
self.sync_append = self.sync.append
# XXX-Aurel : must be renamed to buildSyncMLDeletion & moved
def addDeleteCommand(self, gid=None):
"""
Delete an object with the SyncML protocol
"""
self._initSyncTag()
xml = (E.Delete(
E.CmdID(
self._getNextCommandId()),
E.Item(
E.Source(E.LocURI('%s' % gid))
)
))
self.sync_append(xml)
def addConfirmationMessage(self, command, sync_code, target_ref=None,
source_ref=None, command_ref=None,
message_ref=None):
"""
This is used in order to confirm that an object was correctly
synchronized
"""
xml = E.Status()
xml.append(E.CmdID(self._getNextCommandId()))
if message_ref:
xml.append(E.MsgRef(str(message_ref)))
if command_ref:
xml.append(E.CmdRef(str(command_ref)))
xml.append(E.Cmd(command))
# Add either target ou source
if target_ref:
xml.append(E.TargetRef(target_ref))
if source_ref:
xml.append(E.SourceRef(source_ref))
xml.append(E.Data(resolveSyncmlStatusCode(sync_code)))
self.body_append(xml)
self.sync_confirmation_counter += 1
class SyncMLRequest(object):
""" SyncMLRequest represent a message received by the client or server"""
def __init__(self, xml):
if isinstance(xml, basestring):
self.data = etree.XML(xml, parser=parser)
else:
raise ValueError("Do not know how to initialize message with data %r"
% (xml,))
# Define default values for all variables that will contain parsed data
self.credentials = {}
self.header = {}
self.alert_list = []
self.status_list = []
self.sync_command_list = []
self.isFinal = False
self.parse()
def __str__(self):
return etree.tostring(self.data, encoding='utf-8', xml_declaration=True,
pretty_print=True)
def parse(self):
"""
Generic parse method that will call all sub methods to parse all xml data
"""
self._parseHeader()
self._parseCredentials()
self._parseAlertCommand()
self._parseStatusList()
self._parseSyncCommandList()
self._parseFinal()
def _parseFinal(self):
self.isFinal = bool(self.data.xpath(
'/syncml:SyncML/syncml:SyncBody/syncml:Final',
namespaces=self.data.nsmap))
def _parseHeader(self):
self.header = {
'dtd_version': float(self.data.xpath(
'string(/syncml:SyncML/syncml:SyncHdr/syncml:VerDTD)',
namespaces=self.data.nsmap)),
'protocol_version': str(self.data.xpath(
'string(/syncml:SyncML/syncml:SyncHdr/syncml:VerProto)',
namespaces=self.data.nsmap)),
'session_id': int(self.data.xpath(
'string(/syncml:SyncML/syncml:SyncHdr/syncml:SessionID)',
namespaces=self.data.nsmap)),
'message_id': int(self.data.xpath(
'string(/syncml:SyncML/syncml:SyncHdr/syncml:MsgID)',
namespaces=self.data.nsmap)),
'target': str(self.data.xpath(
'string(/syncml:SyncML/syncml:SyncHdr/syncml:Target/syncml:LocURI)',
namespaces=self.data.nsmap)),
'source': str(self.data.xpath(
'string(/syncml:SyncML/syncml:SyncHdr/syncml:Source/syncml:LocURI)',
namespaces=self.data.nsmap)),
}
def _parseCredentials(self):
if len(self.data.xpath('/syncml:SyncML/syncml:SyncHdr/syncml:Cred',
namespaces=self.data.nsmap)):
self.credentials = {
'format': str(self.data.xpath(
'string(/syncml:SyncML/syncml:SyncHdr/syncml:Cred/syncml:Meta/syncml:Format)',
namespaces=self.data.nsmap)),
'type': str(self.data.xpath(
'string(/syncml:SyncML/syncml:SyncHdr/syncml:Cred/syncml:Meta/syncml:Type)',
namespaces=self.data.nsmap)),
'data': str(self.data.xpath(
'string(/syncml:SyncML/syncml:SyncHdr/syncml:Cred/syncml:Data)',
namespaces=self.data.nsmap)),
}
def _parseAlertCommand(self):
# XXX must parse a list of alert commands
base_alert_xpath = "/syncml:SyncML/syncml:SyncBody/syncml:Alert"
append = self.alert_list.append
alert_node_list = self.data.xpath(base_alert_xpath,
namespaces=self.data.nsmap)
for alert in alert_node_list:
alert_kw = {
'command_id': int(alert.xpath('string(./syncml:CmdID)' ,
namespaces=alert.nsmap)),
'message_reference': str(alert.xpath('string(./syncml:MsgRef)' ,
namespaces=alert.nsmap)),
'command_reference': str(alert.xpath('string(./syncml:CmdRef)' ,
namespaces=alert.nsmap)),
'data': str(alert.xpath('string(./syncml:Data)' ,
namespaces=alert.nsmap)),
'target': str(alert.xpath(
'string(./syncml:Item/syncml:Target/syncml:LocURI)',
namespaces=alert.nsmap)),
'source': str(alert.xpath(
'string(./syncml:Item/syncml:Source/syncml:LocURI)',
namespaces=alert.nsmap)),
'last_anchor': str(alert.xpath(
'string(./syncml:Item/syncml:Meta/syncml:Anchor/syncml:Last)',
namespaces=alert.nsmap)),
'next_anchor': str(alert.xpath(
'string(./syncml:Item/syncml:Meta/syncml:Anchor/syncml:Next)',
namespaces=alert.nsmap)),
}
append(alert_kw)
def _parseStatusList(self):
append = self.status_list.append
status_node_list = self.data.xpath('//syncml:Status', namespaces=self.data.nsmap)
for status in status_node_list:
status_kw = {
"command_id": str(status.xpath('string(./syncml:Cmd)',
namespaces=self.data.nsmap)),
"message_reference" : str(status.xpath('string(./syncml:MsgRef)',
namespaces=self.data.nsmap)),
"command_reference" : str(status.xpath('string(./syncml:CmdRef)',
namespaces=self.data.nsmap)),
"command" : str(status.xpath('string(./syncml:Cmd)',
namespaces=self.data.nsmap)),
"status_code" : str(status.xpath('string(./syncml:Data)',
namespaces=self.data.nsmap)),
"target" : str(status.xpath('string(./syncml:TargetRef)',
namespaces=self.data.nsmap)),
"source" : str(status.xpath('string(./syncml:SourceRef)',
namespaces=self.data.nsmap)),
"anchor" : str(status.xpath(
'string(./syncml:Item/syncml:Data/syncml:Anchor)',
namespaces=self.data.nsmap)),
"authentication_format" : str(status.xpath(
'string(./syncml:Chal/syncml:Meta/syncml:Format)',
namespaces=self.data.nsmap)),
"authentication_type" : str(status.xpath(
'string(./syncml:Chal/syncml:Meta/syncml:Type)',
namespaces=self.data.nsmap))
}
append(status_kw)
def _parseSyncCommandList(self):
append = self.sync_command_list.append
for sync_command in self.data.xpath(
'//syncml:Add|//syncml:Delete|//syncml:Replace',
namespaces=self.data.nsmap):
sync_command_kw = {
"command_id" : str(sync_command.xpath(
'string(.//syncml:CmdID)',
namespaces=self.data.nsmap)),
"source" : str(sync_command.xpath(
'string(.//syncml:Item/syncml:Source/syncml:LocURI)',
namespaces=self.data.nsmap)),
"target" : str(sync_command.xpath(
'string(.//syncml:Item/syncml:Target/syncml:LocURI)',
namespaces=self.data.nsmap)),
"command" : str(sync_command.xpath(
'local-name()',
namespaces=self.data.nsmap)),
"more_data" : bool(sync_command.xpath(
'.//syncml:Item/syncml:MoreData',
namespaces=self.data.nsmap))
}
# Get xml data apart, we must render it as string for activity
xml_data = sync_command.xpath('.//syncml:Item/syncml:Data/*',
namespaces=self.data.nsmap)
if xml_data:
sync_command_kw["xml_data"] = etree.tostring(xml_data[0])
else:
# If not xml, return raw data
# XXX this is unicode and can be a problem for activity
sync_command_kw["raw_data"] = sync_command.xpath(
'string(.//syncml:Item/syncml:Data)',
namespaces=self.data.nsmap)
append(sync_command_kw)
This source diff could not be displayed because it is too large. You can view the blob instead.
# -*- coding: utf-8 -*-
## Copyright (c) 2012 Nexedi SARL and Contributors. All Rights Reserved.
# Aurélien Calonne <aurel@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.
#
##############################################################################
from HTTP import ConnectionError
class FileTransport:
def send(self, to_url, data, sync_id, content_type):
filename = to_url[len('file:/'):]
try:
stream = file(filename, 'w')
stream.write(data)
stream.close()
except IOError:
raise ConnectionError
# -*- coding: utf-8 -*-
## Copyright (c) 2012 Nexedi SARL and Contributors. All Rights Reserved.
# Aurélien Calonne <aurel@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.
#
##############################################################################
from os import environ
from logging import getLogger
from requests import post
syncml_logger = getLogger('ERP5SyncML')
class ConnectionError(Exception):
pass
class HTTPTransport:
def getProxyMapping(self):
"""
Return a mapping of protocol to proxy
Based on environment variable
"""
return {'http' : environ.get('http_proxy'),
'https' : environ.get('https_proxy'),
}
def getHeaders(self, content_type, xml):
"""
SyncML defined specifications about http bindings
Follow advices given about headerds
"""
return {
# XXX- Syncml content-type is not supported by Zope server
# for now disable it until ZServer get patched
# 'Content-Type' : content_type,
# 'Content-Length' : str(len(xml)), XXX-Bad way to compute length
'Cache-control' : 'no-store',
'Transfer-encoding' : 'chunked',
  • Why chunked ? Neither our code nor requests encodes the body this way and waitress (WSGI) considers the request is corrupted.

    It worked so far because Medusa does not seem to understand the chunked transfer encoding. Can I simply remove this line ?

    At the same time, I would also remove the commented Content-Length, because requests fills it correctly.

Please register or sign in to reply
'Accept' : 'application/vnd.syncml+xml',
'Accept-Charset' : 'UTF-8',
'User-Agent' : 'ERP5SyncML Tool',
}
def send(self, to_url, data, sync_id, content_type):
syncml_logger.debug("HTTP.send : %s" %(to_url,))
data = {
'text' : data,
'sync_id': sync_id
}
r = post(to_url,data=data,
headers=self.getHeaders(content_type, data),
timeout=60,
proxies=self.getProxyMapping())
syncml_logger.debug("Status code : %s - %s" %(r.status_code, r.headers))
r.raise_for_status()
# -*- coding: utf-8 -*-
## Copyright (c) 2012 Nexedi SARL and Contributors. All Rights Reserved.
# Aurélien Calonne <aurel@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.
#
##############################################################################
from HTTP import ConnectionError
from Products.ERP5.ERP5Site import getSite
class MailTransport:
def send(self, from_url, to_url, xml, sync_id, content_type):
to_address = to_url[len('mailto:'):]
from_address = from_url[len('mailto:'):]
try:
getSite().sendMail(from_address, to_address, sync_id, xml)
except:
raise ConnectionError
......@@ -28,18 +28,28 @@
##############################################################################
import smtplib
from Products.CMFCore.utils import getToolByName
from ERP5Diff import ERP5Diff
from zLOG import LOG, INFO
import os
from base64 import b64encode, b64decode
from lxml import etree
from lxml.builder import ElementMaker
from zLOG import LOG, INFO
from ERP5Diff import ERP5Diff
from DateTime import DateTime
from SyncMLConstant import SYNCML_NAMESPACE, NSMAP, MAX_LEN
from Products.ERP5.ERP5Site import getSite
E = ElementMaker(namespace=SYNCML_NAMESPACE, nsmap=NSMAP)
parser = etree.XMLParser(remove_blank_text=True)
from base64 import b64encode, b64decode
from imp import load_source
def buildAnchorFromDate(date):
if isinstance(date, DateTime):
date = date.HTML4()
date = date.replace("-", "")
date = date.replace(":", "")
return date
def encode(format, string_to_encode):
"""
......@@ -82,7 +92,6 @@ def xml2wbxml(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()
......@@ -97,7 +106,6 @@ def wbxml2xml(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()
......@@ -131,109 +139,40 @@ def getConduitByName(conduit_name):
conduit_instance = getattr(conduit_module, conduit_name)()
return conduit_instance
def resolveSyncmlStatusCode(context, category_id):
def resolveSyncmlStatusCode(category_id):
"""Return reference of syncml_status_code category
"""
category_tool = getToolByName(context.getPortalObject(), 'portal_categories')
return category_tool.getCategoryValue(category_id,
base_category='syncml_status_code')\
.getReference()
try:
return str(int(category_id))
except ValueError:
return getSite().portal_categories.getCategoryValue(
category_id,
base_category='syncml_status_code').getReference()
def resolveSyncmlAlertCode(portal, category_id):
def resolveSyncmlAlertCode(category_id):
"""Return reference of syncml_alert_code category
"""
category_tool = getToolByName(portal, 'portal_categories')
return category_tool.getCategoryValue(category_id,
base_category='syncml_alert_code')\
.getReference()
def getAlertCodeFromXML(xml):
"""
Return the value of the alert code inside the full syncml message
"""
alert_code = '%s' % xml.xpath('string(/syncml:SyncML/syncml:SyncBody/'\
'syncml:Alert/syncml:Data)',
namespaces=xml.nsmap)
return alert_code
try:
return str(int(category_id))
except ValueError:
return getSite().portal_categories.getCategoryValue(
category_id,
base_category='syncml_alert_code').getReference()
def checkAlert(xml):
"""
Check if there's an Alert section in the xml_stream
"""
return bool(xml.xpath('string(/syncml:SyncML/syncml:SyncBody/syncml:Alert)',
namespaces=xml.nsmap))
def getMessageIdFromXml(xml):
"""
We will retrieve the message id of the message
"""
return int(xml.xpath('string(/syncml:SyncML/syncml:SyncHdr/syncml:MsgID)',
namespaces=xml.nsmap))
def getSubscriptionUrlFromXML(xml):
"""return the source URI of the syncml header
"""
return '%s' % xml.xpath('string(//syncml:SyncHdr/syncml:Source/'\
'syncml:LocURI)', namespaces=xml.nsmap)
def checkFinal(xml):
"""
Check if there's an Final section in the xml_stream
The end sections (inizialisation, modification) have this tag
"""
return bool(xml.xpath('/syncml:SyncML/syncml:SyncBody/syncml:Final',
namespaces=xml.nsmap))
def setRidWithMap(xml, 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.xpath('/syncml:SyncML/syncml:SyncBody/syncml:Map/syncml:MapItem',
namespaces=xml.nsmap)
for map_item in item_list:
gid = '%s' % map_item.xpath('string(.//syncml:Target/syncml:LocURI)', namespaces=xml.nsmap)
signature = subscriber.getSignatureFromGid(gid)
rid = '%s' % map_item.xpath('string(.//syncml:Source/syncml:LocURI)', namespaces=xml.nsmap)
signature.setRid(rid)
def getSyncBodyStatusList(xml):
"""
return the list of dictionary corredponding to the data of each status bloc
the data are : cmd, code and source
"""
status_list = []
status_node_list = xml.xpath('//syncml:Status', namespaces=xml.nsmap)
for status in status_node_list:
tmp_dict = {}
tmp_dict['cmd'] = '%s' % status.xpath('string(./syncml:Cmd)',
namespaces=xml.nsmap)
tmp_dict['code'] = '%s' % status.xpath('string(./syncml:Data)',
namespaces=xml.nsmap)
tmp_dict['source'] = '%s' % status.xpath('string(./syncml:SourceRef)',
namespaces=xml.nsmap)
tmp_dict['target'] = '%s' % status.xpath('string(./syncml:TargetRef)',
namespaces=xml.nsmap)
status_list.append(tmp_dict)
return status_list
def getDataText(xml):
"""
return the section data in text form, it's usefull for the VCardConduit
"""
return '%s' % xml.xpath('string(.//syncml:Item/syncml:Data)',
namespaces=xml.nsmap)
# def setRidWithMap(xml, 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.xpath('/syncml:SyncML/syncml:SyncBody/syncml:Map/syncml:MapItem',
# namespaces=xml.nsmap)
# for map_item in item_list:
# gid = '%s' % map_item.xpath('string(.//syncml:Target/syncml:LocURI)', namespaces=xml.nsmap)
# signature = subscriber.getSignatureFromGid(gid)
# rid = '%s' % map_item.xpath('string(.//syncml:Source/syncml:LocURI)', namespaces=xml.nsmap)
# signature.setRid(rid)
def getDataSubNode(xml):
"""
Return the content of syncml stream
"""
object_node_list = xml.xpath('.//syncml:Item/syncml:Data/*[1]',
namespaces=xml.nsmap)
if object_node_list:
return object_node_list[0]
return None
def getXupdateObject(object_xml=None, old_xml=None):
"""
......@@ -242,27 +181,20 @@ def getXupdateObject(object_xml=None, old_xml=None):
erp5diff = ERP5Diff()
erp5diff.compare(old_xml, object_xml)
#Upper version of ERP5Diff produce valid XML.
if erp5diff._getResultRoot():
xupdate = erp5diff.outputString()
#omit xml declaration
xupdate = xupdate[xupdate.find('<xupdate:modifications'):]
return xupdate
#def cutXML(xml_string):
#"""
#Sliced a xml tree a return two fragment
#"""
#line_list = xml_string.split('\n')
#short_string = '\n'.join(line_list[:MAX_LINES])
#rest_string = '\n'.join(line_list[MAX_LINES:])
#xml_string = etree.CDATA(short_string.decode('utf-8'))
#return xml_string, rest_string
def cutXML(xml_string):
def cutXML(xml_string, length=None):
"""
Sliced a xml tree a return two fragment
"""
short_string = xml_string[:MAX_LEN]
rest_string = xml_string[MAX_LEN:]
if length is None:
length = MAX_LEN
short_string = xml_string[:length]
rest_string = xml_string[length:]
xml_string = etree.CDATA(short_string.decode('utf-8'))
return xml_string, rest_string
......@@ -284,72 +216,3 @@ class XMLSyncUtilsMixin(object):
#server.sendmail(fromaddr, "seb@localhost", msg)
server.quit()
#def getNamespace(self, nsmap):
#"""
#Set the namespace prefix, check if argument is conform
#and return the full namespace updated for syncml
#nsmap -- The namespace of the received xml
#"""
##search urn compatible in the namespaces of nsmap
#urns = filter(lambda v: v.upper() in self.URN_LIST, nsmap.values())
#if urns:
#namespace = etree.FunctionNamespace(urns[0])
#namespace.prefix = 'syncml'
#return namespace
#else:
#raise ValueError, "Sorry, the given namespace is not supported"
def getStatusTarget(self, xml):
"""
Return the value of the alert code inside the xml_stream
"""
return '%s' % xml.xpath('string(syncml:TargetRef)', namespaces=xml.nsmap)
def getStatusCode(self, xml):
"""
Return the value of the alert code inside the xml_stream
"""
status_code = '%s' % xml.xpath('string(syncml:Data)', namespaces=xml.nsmap)
if status_code:
return int(status_code)
return None
def getStatusCommand(self, xml):
"""
Return the value of the command inside the xml_stream
"""
cmd = None
if xml.xpath('local-name()') == 'Status':
cmd = '%s' % xml.xpath('string(syncml:Cmd)', namespaces=xml.nsmap)
return cmd
def checkStatus(self, xml):
"""
Check if there's a Status section in the xml_stream
"""
return bool(xml.xpath('string(/syncml:SyncML/syncml:SyncBody/syncml:Status)',
namespaces=xml.nsmap))
def getActionType(self, xml):
"""
Return the type of the object described by the action
"""
return '%s' % xml.xpath('string(.//syncml:Meta/syncml:Type)',
namespaces=xml.nsmap)
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
......@@ -28,8 +28,6 @@
from Products.ERP5SyncML.XMLSyncUtils import XMLSyncUtilsMixin
from xml.dom.ext.reader.Sax2 import FromXml
from Products.ERP5SyncML.SyncCode import SyncCode
from zLOG import LOG
class XupdateUtils(XMLSyncUtilsMixin):
"""
......
......@@ -42,9 +42,6 @@ from Products.ERP5Type.Utils import initializeProduct, updateGlobals
document_classes = updateGlobals(this_module, globals(),
permissions_module=Permissions)
import PropertySheet
import interfaces
def initialize( context ):
# Import Product Components
from Tool import SynchronizationTool
......
<dtml-comment>
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
This program as such is not intended to be used by end users. 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.
</dtml-comment>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<h3> <code>portal_synchronization</code> Tool </h3>
<p> This tool embodies site-wide policies concerning synchronization.
You can create a Subscription if you want to subscibe to some objects,
or a Publication on a server if you want to publish some objects.</br></br>
Subscription's elements :</br>
<b>Title :</b> the title used to identify the Subscription</br>
<b>Use Activity :</b> activate or not the use of activity.</br>
<b>Alert Code :</b> Synchronisation type.</br>
<u>Two Way</u> : A normal sync type in which the client and the server exchange information about modified data in these devices. The client sends the modifications first.<br/>
<u>One way From Server</u> : A sync type in which the client gets all modifications from the server but the client does not send its modifications to the server.<br/>
<b>Publication Url :</b> the server's adrress mail</br>
<b>Subscription Url :</b> the client's address mail</br>
<b>Destination Path :</b> the path where are yours objects</br>
<b>Source URI :</b> source URI of synchronization (in case of
synchronization between two erp5 sites, it's should be the same
as the <b>Title</b>)</br>
<b>Target URI :</b> target URI of synchronization (in case of
synchronization between two erp5 sites, it's should be the same
as the <b>Title</b>)</br>
<b>Query :</b> the type of objects you want to synchronize</br>
<b>XML Mapping :</b> the page template used on each object before
an export</br>
<b>Synchronize with other ERP5 sites :</b> The method currently used
to send data to erp5 and external sites is not the same (it will be
improved soon)</br>
<b>Content Type :</b> the type of content use to exchange data
(could be 'application/vnd.syncml+wbxml' or
'application/vnd.syncml+xml' for example)</br>
<b>Conduit :</b> the conduit used to synchronize</br>
<b>GPG key name :</b>a name of gpg key to use</br>
<b>Id Generator :</b> This set the method name wich allows to
generate a new id</br>
<b>Gid Generator :</b> This set the method name wich allows to
find a gid from any object</br>
<b>Media Type :</b> the type of media exchanged between publication
and subscription.</br>
<b>Login :</b> the login used to authenticate on the server (could be
empty if there is no authentication on the server)</br>
<b>Password :</b> the password corresponding to the login</br>
</br>
Publication's elements :</br>
<b>Title :</b> the title used to identify the Subscription</br>
<b>Use Activity :</b> activate or not the use of activity.</br>
<b>Publication Url :</b> the server's adrress mail</br>
<b>Destination Path :</b> the path where are yours objects</br>
<b>Source URI :</b> source URI of synchronization (in case of
synchronization between two erp5 sites, it's should be the same
as the <b>Title</b>)</br>
<b>Query :</b> the type of objects you want to synchronize</br>
<b>XML Mapping :</b> the page template used on each object before
an export</br>
<b>Synchronize with other ERP5 sites :</b> The method currently used
to send data to erp5 and external sites is not the same (it will be
improved soon)</br>
<b>Content Type :</b> the type of content use to exchange data
(could be 'application/vnd.syncml+wbxml' or
'application/vnd.syncml+xml' for example)</br>
<b>Conduit :</b> the conduit used to synchronize</br>
<b>GPG key name :</b>a name of gpg key to use</br>
<b>Id Generator :</b> This set the method name wich allows to
generate a new id</br>
<b>Gid Generator :</b> This set the method name wich allows to
find a gid from any object</br>
<b>Media Type :</b> the type of media exchanged between publication
and subscription.</br>
<b>Authentication Format :</b>the format used to encode the
login and password</br>
<b>Autentication Type :</b> the type of the authentication</br>
</br>
</p>
<dtml-var manage_page_footer>
<dtml-comment>
Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
Jean-Paul Smets <jp@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
This program as such is not intended to be used by end users. 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.
</dtml-comment>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<h3>Conflicts</h3>
<table cellspacing="0" cellpadding="2" border="1">
<tr>
<th align="left" valign="top"></th>
<th align="left" valign="top">Side</th>
<th align="left" valign="top">Object path</th>
<th align="left" valign="top">Property Id</th>
<th align="left" valign="top">Publisher Value</th>
<th align="left" valign="top">Subscriber Value</th>
</tr>
<dtml-in prefix="loop" expr="getConflictList()">
<tr>
<td align="left" valign="top"><a href="managePublisherValue?subscription_url=<dtml-var expr="loop_item.getSubscriber().getSubscriptionUrl()">&property_id=<dtml-var expr="loop_item.getPropertyId()">&object_path=<dtml-var "'/'.join(object_path)">">Publisher</a> <a href="manageSubscriberValue?subscription_url=<dtml-var expr="loop_item.getSubscriber().getSubscriptionUrl()">&property_id=<dtml-var expr="loop_item.getPropertyId()">&object_path=<dtml-var "'/'.join(object_path)">">Subscriber</a></td>
<td align="left" valign="top"><dtml-var expr="loop_item.getSubscriber().getSubscriptionUrl()"></td>
<td align="left" valign="top"><dtml-var "'/'.join(object_path)"></td>
<td align="left" valign="top"><dtml-var expr="loop_item.getPropertyId()"></td>
<td align="left" valign="top"><dtml-var expr="loop_item.getPublisherValue()"></td>
<td align="left" valign="top"><dtml-var expr="loop_item.getSubscriberValue()"></td>
</tr>
</dtml-in>
</table>
<dtml-var manage_page_footer>
<dtml-comment>
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
This program as such is not intended to be used by end users. 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.
</dtml-comment>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="manage_addPublicationForm">
<input type="submit" name="manage_addPublication:method" value="Add a publication">
</form>
<dtml-in getPublicationList>
<h3>Publication <dtml-var sequence-index></br></h3>
<form action="manage_editPublication" method="POST">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" value="<dtml-var getTitle>" size="40"/>
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Use Activity
</label></div>
</td>
<td align="left" valign="top">
<input type="checkbox" name="activity_enabled" value="1" <dtml-if expr="getActivityEnabled()">CHECKED</dtml-if>>
</td>
</tr>
<tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Publication Url
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="publication_url" value="<dtml-var getPublicationUrl>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Destination Path
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="destination_path" value="<dtml-var getDestinationPath>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Source URI
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="source_uri" size="40" value="<dtml-var getSourceURI>" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Query
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="query" value="<dtml-var getQuery>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
XML mapping
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="xml_mapping" value="<dtml-var getXMLMapping>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Synchronize with other ERP5 sites
</label></div>
</td>
<td align="left" valign="top">
<input type="checkbox" name="synchronize_with_erp5_sites" value="1" <dtml-if expr="getSynchronizeWithERP5Sites()">CHECKED</dtml-if>>
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Content Type
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="sync_content_type" value="<dtml-var getSyncContentType>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Conduit
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="conduit" value="<dtml-var getConduit>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
GPG key name
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="gpg_key" value="<dtml-var getGPGKey>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Id Generator
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="synchronization_id_generator" value="<dtml-var getSynchronizationIdGenerator>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Media Type
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="media_type" value="<dtml-var getMediaType>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Authentication Format
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="authentication_format" value="<dtml-var getAuthenticationFormat>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Authentication Type
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="authentication_type" value="<dtml-var getAuthenticationType>" size="40" />
</td>
</tr>
</table>
<table>
<tr>
<td align="left" valign="top">
<input type="submit" name="submit" value=" Change " />
</form>
</td>
<form action="manage_resetPublication" method="POST">
<td align="left" valign="top">
<input type="submit" value=" Reset ">
<input type="hidden" name="title" value="<dtml-var getTitle>" >
</form>
<form action="manage_deletePublication" method="POST">
<td align="left" valign="top">
<input type="submit" value=" Delete ">
<input type="hidden" name="title" value="<dtml-var getTitle>" >
</form>
</td>
</tr>
</table>
</form>
</dtml-in>
<dtml-var manage_page_footer>
<dtml-comment>
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
This program as such is not intended to be used by end users. 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.
</dtml-comment>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="manage_addSubscriptionForm">
<input type="submit" name="manage_addSubscriptionForm:method" value="Add a Subscription">
</form>
<dtml-in getSubscriptionList>
<h3>Subscription <dtml-var sequence-index></h3>
<br/>
<form action="manage_editSubscription" method="POST">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td><div class="form-label">Last Anchor</div></td>
<td><dtml-var getLastAnchor></td>
</tr>
<tr>
<td><div class="form-label">Next Anchor</div></td>
<td><dtml-var getNextAnchor></td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" value="<dtml-var getTitle>" size="40"/>
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Use Activity
</label></div>
</td>
<td align="left" valign="top">
<input type="checkbox" name="activity_enabled" value="1" <dtml-if expr="getActivityEnabled()">CHECKED</dtml-if>>
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Alert Code<br/>
Two way = 200<br/>
One Way From Server = 204
</label></div>
</td>
<td align="left" valign="top">
<select name="alert_code">
<dtml-in getAlertCodeList>
<dtml-let item=sequence-item>
<option value="<dtml-var item>" <dtml-if expr="getAlertCode() == item">SELECTED</dtml-if>><dtml-var item></option>
</dtml-let>
</dtml-in>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Publication Url
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="publication_url" value="<dtml-var getPublicationUrl>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Subscription Url
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="subscription_url" value="<dtml-var getSubscriptionUrl>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Destination path
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="destination_path" value="<dtml-var getDestinationPath>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Source URI
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="source_uri" size="40" value="<dtml-var getSourceURI>" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Target URI
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="target_uri" size="40" value="<dtml-var getTargetURI>" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Query
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="query" value="<dtml-var getQuery>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
XML mapping
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="xml_mapping" value="<dtml-var xml_mapping>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Synchronize with other ERP5 sites
</label></div>
</td>
<td align="left" valign="top">
<input type="checkbox" name="synchronize_with_erp5_sites" value="1" <dtml-if expr="getSynchronizeWithERP5Sites()">CHECKED</dtml-if>>
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Content Type
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="sync_content_type" value="<dtml-var getSyncContentType>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Conduit
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="conduit" value="<dtml-var getConduit>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
GPG key name
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="gpg_key" value="<dtml-var getGPGKey>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Id Generator
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="synchronization_id_generator" value="<dtml-var getSynchronizationIdGenerator>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Media Type
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="media_type" value="<dtml-var getMediaType>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Login
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="login" value="<dtml-var getLogin>" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Password
</label></div>
</td>
<td align="left" valign="top">
<input type="password" name="password" value="<dtml-var getPassword>" size="40" />
</td>
</tr>
</table>
<table>
<tr>
<td align="left" valign="top">
<input type="submit" name="submit" value=" Change " />
</form>
</td>
<form action="manage_syncSubscription" method="POST">
<td align="left" valign="top">
<input type="submit" value=" Sync ">
<input type="hidden" name="title" value="<dtml-var getTitle>" >
</form>
<form action="manage_resetSubscription" method="POST">
<td align="left" valign="top">
<input type="submit" value=" Reset ">
<input type="hidden" name="title" value="<dtml-var getTitle>" >
</form>
<form action="manage_deleteSubscription" method="POST">
<td align="left" valign="top">
<input type="submit" value=" Delete ">
<input type="hidden" name="title" value="<dtml-var getTitle>" >
</form>
</td>
</tr>
</table>
</dtml-in>
<dtml-var manage_page_footer>
<dtml-comment>
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
This program as such is not intended to be used by end users. 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.
</dtml-comment>
<dtml-let form_title="'Add Publication'">
<dtml-var manage_page_header>
<dtml-var manage_form_title>
</dtml-let>
<form action="manage_addPublication" method="POST">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Use Activity
</label></div>
</td>
<td align="left" valign="top">
<input type="checkbox" name="activity_enabled" value="1">
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Publication Url
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="publication_url" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Destination Path
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="destination_path" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Source URI
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="source_uri" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Query
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="query" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
XML mapping
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="xml_mapping" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Synchronize with other ERP5 sites
</label></div>
</td>
<td align="left" valign="top">
<input type="checkbox" name="synchronize_with_erp5_sites" value="1">
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Content Type
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="sync_content_type" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Conduit
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="conduit" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
GPG key name
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="gpg_key" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Id Generator
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="synchronization_id_generator" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Media Type
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="media_type" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Authentication Format
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="authentication_format" size="40" />
</td>
</tr>
<td align="left" valign="top">
<div class="form-label">
Authentication Type
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="authentication_type" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Add " />
</div>
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>
<dtml-comment>
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
This program as such is not intended to be used by end users. 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.
</dtml-comment>
<dtml-let form_title="'Add Subscription'">
<dtml-var manage_page_header>
<dtml-var manage_form_title>
</dtml-let>
<form action="manage_addSubscription" method="POST">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Use Activity
</label></div>
</td>
<td align="left" valign="top">
<input type="checkbox" name="activity_enabled" value="1">
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Alert Code<br/>
Two way = 200<br/>
One Way From Server = 204
</label></div>
</td>
<td align="left" valign="top">
<select name="alert_code">
<dtml-in getAlertCodeList>
<dtml-let item=sequence-item>
<option value="<dtml-var item>"><dtml-var item></option>
</dtml-let>
</dtml-in>
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Publication Url
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="publication_url" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Subscription Url
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="subscription_url" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Destination path
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="destination_path" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Source URI
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="source_uri" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Target URI
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="target_uri" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Query
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="query" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
XML mapping
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="xml_mapping" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Synchronize with other ERP5 sites
</label></div>
</td>
<td align="left" valign="top">
<input type="checkbox" name="synchronize_with_erp5_sites" value="1" checked="checked">
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Content Type
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="sync_content_type" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Conduit
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="conduit" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
GPG key
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="gpg_key" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Id Generator
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="synchronization_id_generator" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Media Type
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="media_type" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Login
</label></div>
</td>
<td align="left" valign="top">
<input type="text" name="login" size="40" /> </td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Password
</label></div>
</td>
<td align="left" valign="top">
<input type="password" name="password" size="40" />
</td>
</tr>
<tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Add " />
</div>
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>
Setting up Synchronization
Installing packages
First install zope and all products you might need (CPS...)
Then install products ERP5Type, CMFActivity, ERP5SyncML.
available with the nexedi cvs.
And also CMFWiki, psyco, available from zope and python. You will
also need xml for python.
We use for the synchronization a tool wich is able to generate
xupdate xml. Xupdate xml is described on this website ::
http://www.xmldb.org/xupdate/
We have made on own tool to generate xupdate xml, so you need
to install the erp5diff software.
If you want the synchronization working with CPS, you will need
need the ERP5CPS products, available on the nexedi cvs.
Install Site instances
If you want to do synchronization, you should have at least two Site
instances (it can actually be either ERP5 Site or CPS Site). One of them
has to be a master and all other have to be clients.
Any modification made on any Site will be sent to the master, and the master
send back each modification to each client. Like this, all Sites (master and
clients) are informed about modifications.
Let's say we have one zope running on our local box. We will create for
example two CPS Sites, the fist one wich will be the master with the id
'cps', and the second one wich will be the client with the id 'cps_client'.
If you want to make sure that your box is installed correctly, you should
go on one object of your site and then addto the url the method 'asXML' like
this ::
http://localhost:9673/cps/workspaces/members/seb/gggggggggggggg/asXML
If everything is fine, you should have no errors and you should see the
xml corresponding to your object.
Installing the synchronization tool
Go to the zope management interface and then add to all your sites (master
and clients) an ERP5SyncML Tool.
Configuration for synchronization by http
The particular thing with the synchronization by http is that we need
to use active objects. Active Object allows to differ a method call.
We need it in order to differ html request, active object allows to
send all html request to a queue.
Don't worry, it is really simple to use. First, you have to add inside
all your sites (master and clients) a 'CMFActivity Tool'. There is nothing
to configure. Then, only thing needed is to modify the script
'zope_tic_loop' located inside your CMFActivity product. Since it requires
many changes you can take my following example and then you have to modify
the user name, the user password, the ip where your zope is running and
the id of your sites.
For example, my script looks like this ::
#!/bin/bash
while true; do
# Site 1 (the master one)
wget -O /tmp/zope_distribute.out http://seb:password@localhost:9673/cps/portal_activities/distribute?node_count:int=1
wget -O /tmp/zope_tic.out http://seb:password@localhost:9673/cps/portal_activities/tic?processing_node:int=1
# Site 2 (the client one)
wget -O /tmp/zope_distribute.out http://seb:password@localhost:9673/cps_client/portal_activities/distribute?node_count:int=1
wget -O /tmp/zope_tic.out http://seb:password@localhost:9673/cps_client/portal_activities/tic?processing_node:int=1
sleep 5
done
This allows to run all html requests each time there is a new one. It is
a nice idea to run the script like this ::
nohup ./zope_tic_loop &
The last thing to do is to configure you synchronization tool inside
the zope management interface. You have in this tool many tabs. The
'publication' tab is used on the server and the 'subscription' tab
is used on all clients. A publication is a setting for something we
want to share with others. So on the master we have to specify as
many publication as the number of folders we want to share. A subscription
is a setting for something we want to synchronize from a master, so on each
client we have to specify as many subscriptions as the number of folders
we want to synchronize with the master.
I do have on my server side the following publication ::
id : Repository
Publication Url : http://localhost:9673/cps
Destination Path : /cps/portal_repository
Query : objectValues (it will be completed automatically)
XML Mapping : asXML
GPG key name : (it can stay empty)
I do have on my client side the following subscription ::
id : Repository
Publication Url : http://localhost:9673/cps
Subscription Url : http://localhost:9673/cps_client/Repository
Destination Path : /cps_client/portal_repository
Query : objectValues (it will be completed automatically)
XML Mapping : asXML
GPG key name : (it can stay empty)
CPS Synchronization
This is just a note for people who wants to synchronize some CPS Sites, you have to
synchronize theses directories ::
/cps_site/portal_repository
/cps_site/workspaces
/cps_site/sections
Starting synchronization
The synchronization starts only from a client, so you should go on one of the client
instance. Then you have to go on the portal_synchronizations and select one of the
subscriptions and hit 'Sync'.
Configuration for synchronization by email
Actually you need to add a 'CMFMailin Tool', there is nothing to configure.
Then you have to make sure you get the right mail_received.py. The
mail_received script has to be modified in order to use SubSync or PubSync.
SubSync is used if we are on the client side, PubSync is used if we are on
the master side.
You can find the mail_received inside the ERP5SyncML/skins/ folder. You can
just make a copy inside your ZODB. For that, you should go to your cps site,
inside portal_skins and then select the "custom" folder. Then add a script
python called "mail_received". Then set the parameter list to "theMail", and
then paste the content of ERP5CPS/skins/mail_received.py .
Inside the synchronization tool (cps_site/portal_synchrozations),
the publication should looks like this ::
id : Repository
Publication Url : mailto:cps_server@localhost
Destination Path : /cps/portal_repository
Query : objectValues (it will be completed automatically)
XML Mapping : asXML
Inside the synchronization tool, the subscription should looks like this ::
id : Repository
Publication Url : mailto:cps_server@localhost
Subscription Url : mailto:cps_client@localhost
Destination Path : /cps_client/portal_repository
Query : objectValues (it will be completed automatically)
XML Mapping : asXML
If you want to synchronize, you should have at least 2 instances of cmf sites. One of
them should act as a server, and all over as client. On the server you have to setup
publications and on client you have to setup subscriptions.
Configure your mail server (email synchronization)
You should set some aliases so that your mail server will directly sends
mail to zope, for example in /etc/aliases :
seb_syncml: "|/home/seb/erp5/CMFMailIn/sendMailToZope.py http://seb:password@localhost:9673/nexedi.org/portal_mailin/"
This is really important that the master and all clients can send mails each others.
On each box I set a local postfix server and I also use fetchmail every 20 secondes in
order to do a synchronization not too long. A nice way to do is to create a specific
user, for example I do have on my box an user 'seb_syncml'. This user have on his
home directory an file .fetchmailrc like this ::
set daemon 20
set syslog
defaults user "seb_syncml"
pass "password"
smtpname "seb_syncml@localhost"
fetchall
poll localhost proto imap port 11143
Synchro historiques
- on souhaite conserver la somme de tous les historiques (sinon, Mme X va
dire: cette commande je l'ai confirmée, et ERP5 a tout perdu)
- on aura des conflits de 2 natures
1- techniques: la suite des transitions est incohérente lors de la synchro
2- sémantiques: deux personnes ont envoyé une information incohérente (ex.
pour informer d'une date de livraison sur une transition stateless)
- on peut imaginer 2 approches:
- mettre un document dans un état "synchronization_conflict" dès que 2
personnes on effectué des actions de workflow techniquement incompatibles
"en même temps" (cas 1). Seule une opération de workflow de résolution de
conflit permet d'en sortir.
- mettre un document dans un état "synchronization_conflict" dès que 2
personnes on effectué des actions de workflow "en même temps" (cas 2).
Seule une opération de workflow de résolution de conflit permet d'en
sortir.
Replay
Chaque transition (du DCWorflow) dispose d'un attribut
"requires_execution_during_synchronization". La synchronisation appelle les
transitions qui nécessitent une exécution locale en fournissation un
paramètre "execution_during_synchronization" à 1.
"execution_during_synchronization" vaut 0 lors d'une transition normale et 1
lors d'une exécution par le moteur de synchro
## Script (Python) "mail_received"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=theMail
##title=
##
id = theMail['headers'].get('subject')
msg = theMail['body']
# Master side, uncoment this if you are the master
#context.portal_synchronizations.PubSync(id,msg=msg)
# Client side, uncoment this if you are the master
#context.portal_synchronizations.SubSync(id,msg=msg)
# the return of None indicates a success
# The return of anything else assumes that you are returning an error message
# and most MTA's will bounce that error message back to the mail sender
return None
root = context.portal_url.getPortalObject()
acl_users = root.acl_users
acl_users.manage_exportObject(id='acl_users')
return "ok"
root = context.portal_url.getPortalObject()
root.manage_delObjects('acl_users')
root.manage_importObject('acl_users.zexp',set_owner=0)
return "ok"
......@@ -28,20 +28,18 @@
##############################################################################
import os
from base64 import b16encode
import unittest
from Testing import ZopeTestCase
from Products.ERP5Type.tests.runUnitTest import tests_home
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase,\
_getConversionServerDict
from AccessControl.SecurityManagement import newSecurityManager
from Products.ERP5SyncML.Conduit.ERP5DocumentConduit import ERP5DocumentConduit
from zLOG import LOG
from base64 import b16encode
from lxml import etree
from Products.ERP5Type.tests.runUnitTest import tests_home
from Products.ERP5Type.tests.ERP5TypeTestCase import _getConversionServerDict
from Products.ERP5Type.tests.utils import FileUpload
from Products.ERP5SyncML import SyncMLConstant
from Products.ERP5SyncML.Tool import SynchronizationTool
from Products.ERP5SyncML.tests.testERP5SyncML import TestERP5SyncMLMixin
from Products.ERP5SyncML.Document import SyncMLSubscription
from Products.ERP5Type.tests.backportUnittest import expectedFailure
test_files = os.path.join(os.path.dirname(__file__), 'test_document')
FILENAME_REGULAR_EXPRESSION = "(?P<reference>[A-Z]{3,10})-\
......@@ -100,32 +98,22 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
xml_mapping = 'asXML'
pub_conduit = 'ERP5DocumentConduit'
sub_conduit1 = 'ERP5DocumentConduit'
activity_enabled = True
publication_url = 'file:/%s/sync_server' % tests_home
subscription_url = {'two_way': 'file:/%s/sync_client1' % tests_home,
'from_server': 'file:/%s/sync_client_from_server' % tests_home}
#for this tests
nb_message_first_synchronization = 12
nb_message_multi_first_synchronization = 14
nb_synchronization = 2
nb_subscription = 1
nb_publication = 1
#default edit_workflow
workflow_id = 'processing_status_workflow'
nb_message_first_synchronization = 6
nb_message_multi_first_synchronization = 12
activity_enable=False
def getBusinessTemplateList(self):
"""
Return the list of business templates.
"""
return ('erp5_base',
'erp5_syncml',
'erp5_ingestion',
'erp5_ingestion_mysql_innodb_catalog',
'erp5_web',
'erp5_dms',
)
return list(TestERP5SyncMLMixin.getBusinessTemplateList(self)) + \
['erp5_ingestion', 'erp5_ingestion_mysql_innodb_catalog',
'erp5_web', 'erp5_dms']
def afterSetUp(self):
"""Setup."""
......@@ -145,6 +133,18 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
self.clearDocumentModules()
self.clearPublicationsAndSubscriptions()
def clearFiles(self):
# reset files, because we do sync by files
for filename in self.subscription_url.values():
file = open(filename[len('file:/'):], 'w')
file.write('')
file.close()
file = open(self.publication_url[len('file:/'):], 'w')
file.write('')
file.close()
def setSystemPreferences(self):
default_pref = self.portal.portal_preferences.default_site_preference
conversion_dict = _getConversionServerDict()
......@@ -156,7 +156,6 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
default_pref.enable()
def addSubscriptions(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
if self.sub_id1 not in portal_sync.objectIds():
subscription = portal_sync.newContent(portal_type='SyncML Subscription',
......@@ -170,14 +169,13 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id=self.sub_conduit1,
sync_alert_code='two_way',
is_activity_enabled=True,
is_activity_enabled=self.activity_enable,
user_id='daniele',
password='myPassword')
subscription.validate()
self.tic()
def addPublications(self):
portal_id = self.getPortalName()
portal_sync = self.getSynchronizationTool()
if self.pub_id not in portal_sync.objectIds():
publication = portal_sync.newContent(portal_type='SyncML Publication',
......@@ -188,7 +186,7 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
list_method_id=self.pub_query,
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id=self.pub_conduit,
is_activity_enabled=True,)
is_activity_enabled=self.activity_enable,)
publication.validate()
self.tic()
......@@ -225,8 +223,6 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
### Usefull methods
####################
def getSynchronizationTool(self):
return getattr(self.portal, 'portal_synchronizations')
def getDocumentClient1(self):
return getattr(self.portal, 'document_client1')
......@@ -237,9 +233,6 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
def getDocumentServer(self):
return getattr(self.portal, 'document_server')
def getPortalId(self):
return self.portal.getId()
def login(self):
uf = self.portal.acl_users
uf._doAddUser('daniele', 'myPassword', ['Manager'], [])
......@@ -260,7 +253,6 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
def documentMultiServer(self):
# create different document by category documents
self.createDocumentModules()
document_id = ''
document_server = self.getDocumentServer()
#plain text document
for id in self.ids[:self.id_max_text]:
......@@ -283,10 +275,7 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
create document in document_server
"""
self.createDocumentModules(one_way)
document_id = ''
document_server = self.getDocumentServer()
if getattr(document_server, self.id1, None) is not None:
self.clearDocumentModules()
document_text = document_server.newContent(id=self.id1,
portal_type='Text')
kw = {'reference': self.reference1, 'Version': self.version1,
......@@ -313,8 +302,6 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
Create a text document
"""
document_server = self.getDocumentServer()
if getattr(document_server, str(id), None) is not None:
self.clearDocumentModules()
doc_text = document_server.newContent(id=id, portal_type=portal_type)
kw = {'reference': reference, 'version': version, 'language': language}
doc_text.edit(**kw)
......@@ -323,101 +310,16 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
doc_text.edit(file=file)
return doc_text
def synchronize(self, id):
"""
This just define how we synchronize, we have
to define it here because it is specific to the unit testing
"""
portal_sync = self.getSynchronizationTool()
subscription = portal_sync[id]
publication = None
for pub in portal_sync.searchFolder(portal_type='SyncML Publication',
source_reference=subscription.getDestinationReference(),
validation_state='validated'):
if pub.getUrlString() == subscription.getUrlString():
publication = pub
self.assertTrue(publication is not None)
# reset files, because we do sync by files
file = open(subscription.getSubscriptionUrlString()[len('file:/'):], 'w')
file.write('')
file.close()
file = open(self.publication_url[len('file:/'):], 'w')
file.write('')
file.close()
self.tic()
nb_message = 1
result = portal_sync.SubSync(subscription.getPath())
while result['has_response']:
portal_sync.PubSync(publication.getPath())
if self.activity_enabled:
self.tic()
result = portal_sync.SubSync(subscription.getPath())
if self.activity_enabled:
self.tic()
nb_message += 1 + result['has_response']
self.tic()
return nb_message
def synchronizeWithBrokenMessage(self, id):
"""
This just define how we synchronize, we have
to define it here because it is specific to the unit testing
"""
portal_sync = self.getSynchronizationTool()
#portal_sync.email = None # XXX To be removed
subscription = portal_sync[id]
publication = None
for pub in portal_sync.searchFolder(portal_type='SyncML Publication',
source_reference=subscription.getDestinationReference(),
validation_state='validated'):
if pub.getUrlString() == subscription.getUrlString():
publication = pub
self.assertTrue(publication is not None)
# reset files, because we do sync by files
file = open(subscription.getSubscriptionUrlString()[len('file:/'):], 'w')
file.write('')
file.close()
file = open(self.publication_url[len('file:/'):], 'w')
file.write('')
file.close()
self.tic()
nb_message = 1
result = portal_sync.SubSync(subscription.getPath())
while result['has_response']:
# We do thing three times, so that we will test
# if we manage well duplicate messages
portal_sync.PubSync(publication.getPath())
if self.activity_enabled:
self.tic()
portal_sync.PubSync(publication.getPath())
if self.activity_enabled:
self.tic()
portal_sync.PubSync(publication.getPath())
if self.activity_enabled:
self.tic()
result = portal_sync.SubSync(subscription.getPath())
if self.activity_enabled:
self.tic()
result = portal_sync.SubSync(subscription.getPath())
if self.activity_enabled:
self.tic()
result = portal_sync.SubSync(subscription.getPath())
if self.activity_enabled:
self.tic()
nb_message += 1 + result['has_response']
self.tic()
return nb_message
def checkSynchronizationStateIsSynchronized(self):
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer()
for document in document_server.objectValues():
state_list = portal_sync.getSynchronizationState(document)
state_list = self.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], 'synchronized')
document_client1 = self.getDocumentClient1()
for document in document_client1.objectValues():
state_list = portal_sync.getSynchronizationState(document)
state_list = self.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], 'synchronized')
# Check for each signature that the tempXML is None
......@@ -462,7 +364,7 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
self.assertXMLViewIsEqual(self.sub_id1, doc2_s,
document_client1._getOb(self.id2))
def checkDocument(self, id=id, document=None, filename=None,
def checkDocument(self, id, document, filename=None,
size_filename=None, reference='P-SYNCML.Text',
portal_type='Text', version='001', language='en',
description=''):
......@@ -479,7 +381,7 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
self.assertEqual(document.getFilename(), filename)
self.assertEquals(size_filename, document.get_size())
else:
self.fail("Document is None for check this information")
self.fail("Document is None to check this information")
def checkXMLsSynchronized(self):
document_server = self.getDocumentServer()
......@@ -493,9 +395,8 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
def checkFirstSynchronizationWithMultiDocument(self, nb_document=0):
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync[self.sub_id1]
self.assertEqual(len(subscription1.getObjectList()), nb_document)
self.assertEqual(len(subscription1.getDocumentList()), nb_document)
document_server = self.getDocumentServer()
document_client1 = self.getDocumentClient1()
id = str(self.ids[0])
doc_text_s = document_server._getOb(id)
reference = 'Test-Text-%s' % id
......@@ -515,44 +416,33 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
def getTitle(self):
"""
"""
return "ERP5 Document SyncML"
def setupPublicationAndSubscriptionIdGenerator(self):
portal_sync = self.getSynchronizationTool()
sub1 = portal_sync[self.sub_id1]
pub = portal_sync[self.pub_id]
pub.setSynchronizationIdGeneratorMethodId('generateNewId')
sub1.setSynchronizationIdGeneratorMethodId('generateNewId')
def checkSynchronizationStateIsConflict(self, portal_type='Text'):
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer()
for document in document_server.objectValues():
if document.getId()==self.id1:
state_list = portal_sync.getSynchronizationState(document)
state_list = self.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], 'conflict')
document_client1 = self.getDocumentClient1()
for document in document_client1.objectValues():
if document.getId()==self.id1:
state_list = portal_sync.getSynchronizationState(document)
state_list = self.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], 'conflict')
# make sure sub object are also in a conflict mode
document = document_client1._getOb(self.id1)
state_list = portal_sync.getSynchronizationState(document)
state_list = self.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], 'conflict')
def test_01_GetSynchronizationList(self):
# This test the getSynchronizationList, ie,
# We want to see if we retrieve both the subscription
# and the publication
portal_sync = self.getSynchronizationTool()
synchronization_list = portal_sync.contentValues()
self.assertEqual(len(synchronization_list), self.nb_synchronization)
def test_02_FirstSynchronization(self):
# We will try to populate the folder document_client1
......@@ -570,16 +460,20 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
self.checkSynchronizationStateIsSynchronized()
self.checkFirstSynchronization(nb_document=nb_document)
@expectedFailure
def test_03_UpdateSimpleData(self):
# Add two objects
self.test_02_FirstSynchronization()
# First we do only modification on server
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer()
document_s = document_server._getOb(self.id1)
# We modified GID information so we get
# - deletion of former document
# - addition of new document
kw = {'reference':self.reference3, 'language':self.language3,
'version':self.version3}
document_s.edit(**kw)
self.tic()
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
document_client1 = self.getDocumentClient1()
......@@ -638,24 +532,24 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
portal_sync = self.getSynchronizationTool()
publication = portal_sync[self.pub_id]
subscription1 = portal_sync[self.sub_id1]
self.assertEqual(len(publication.getObjectList()), 0)
self.assertEqual(len(subscription1.getObjectList()), 0)
self.assertEqual(len(publication.getDocumentList()), 0)
self.assertEqual(len(subscription1.getDocumentList()), 0)
def test_05_FirstMultiSynchronization(self):
#Add document on the server and first synchronization for client
# Add document on the server and first synchronization for client
nb_document = self.documentMultiServer()
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer()
document_client = self.getDocumentClient1()
nb_message1 = self.synchronize(self.sub_id1)
self.assertNotEqual(nb_message1, 6)
self.synchronize(self.sub_id1)
# It has transmitted some object
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
self.assertEquals(sub.getSyncmlAlertCode(), 'two_way')
self.checkSynchronizationStateIsSynchronized()
self.checkFirstSynchronizationWithMultiDocument(nb_document=nb_document)
@expectedFailure
def test_06_UpdateMultiData(self):
# XXX This tests modify GID of document and so signature
# get added and removed, due to bad behaviour in conduit, it fails
# Add various data in server
# modification in client and server for synchronize
self.test_05_FirstMultiSynchronization()
......@@ -759,10 +653,8 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
self.assertEqual(len(publication['1']), nb_document)
gid = self.reference1 + '-' + self.version1 + '-' + self.language1 # ie the title ''
gid = b16encode(gid)
document_c1 = subscription1.getObjectFromGid(gid)
id_c1 = document_c1.getId()
self.assertTrue(id_c1 in ('1', '2')) # id given by the default generateNewId
document_s = publication.getSubscriber(self.subscription_url['two_way']).getObjectFromGid(gid)
document_c1 = subscription1.getDocumentFromGid(gid)
document_s = publication.getSubscriber(self.subscription_url['two_way']).getDocumentFromGid(gid)
id_s = document_s.getId()
self.assertEqual(id_s, self.id1)
# This will test updating object
......@@ -775,72 +667,78 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c1)
# This will test deleting object
document_server = self.getDocumentServer()
document_client1 = self.getDocumentClient1()
document_server.manage_delObjects(self.id2)
self.tic()
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
self.assertEqual(len(subscription1.getObjectList()), (nb_document-1))
self.assertEqual(len(publication.getObjectList()), (nb_document-1))
document_s = publication.getSubscriber(self.subscription_url['two_way']).getObjectFromGid(gid)
self.assertEqual(len(subscription1.getDocumentList()), (nb_document-1))
self.assertEqual(len(publication.getDocumentList()), (nb_document-1))
document_s = publication.getSubscriber(self.subscription_url['two_way']).getDocumentFromGid(gid)
id_s = document_s.getId()
self.assertEqual(id_s, self.id1)
document_c1 = subscription1.getObjectFromGid(gid)
id_c1 = document_c1.getId()
self.assertTrue(id_c1 in ('1', '2')) # id given by the default generateNewId
document_c1 = subscription1.getDocumentFromGid(gid)
self.assertEqual(document_s.getDescription(), self.description3)
self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c1)
@expectedFailure
def test_08_MultiNodeConflict(self):
"""
We will create conflicts with 3 differents nodes, and we will
solve it by taking one full version of documents.
"""
#not conflict because is gid
# XXX-Aurel Note that this test does no do what it describes !
# Only conflict between one client and one server is done
# so this is node multinodes at all !!
# Do a first synchronization
self.test_02_FirstSynchronization()
portal_sync = self.getSynchronizationTool()
# Then modify data on both side to generate conflicts on different
# properties of the same document
# Modify on server side
document_server = self.getDocumentServer()
document_s = document_server._getOb(self.id1)
kw = {'description':self.description2, 'short_title':self.short_title2}
kw = {'description': self.description2, 'short_title': self.short_title2 }
document_s.edit(**kw)
file = makeFileUpload(self.filename_ppt)
# XXX error with filename_pdf , may be is a PDF?
document_s.edit(file=file)
self.tic()
# Modify on client side
document_client1 = self.getDocumentClient1()
document_c1 = document_client1._getOb(self.id1)
kw = {'description':self.description3, 'short_title':self.short_title3}
kw = {'description': self.description3, 'short_title': self.short_title3 }
document_c1.edit(**kw)
file = makeFileUpload(self.filename_odt)
document_c1.edit(file=file)
self.tic()
self.synchronize(self.sub_id1)
conflict_list = portal_sync.getConflictList()
self.assertEqual(len(conflict_list), 9)
# Check conflicts generated
conflict_list = self.getSynchronizationTool().getConflictList()
self.assertEqual(len(conflict_list), 8)
self.assertEqual(sorted([x.getPropertyId() for x in conflict_list]),
['base_data', 'content_md5', 'content_type',
['content_md5', 'content_type',
'data', 'description', 'filename', 'short_title',
'size', 'title'])
# check if we have the state conflict on all clients
self.checkSynchronizationStateIsConflict()
# we will take :
# description et file on document_server
# Fix conflict :
# apply description & file property on document_server
# short_title on document_client1
for conflict in conflict_list :
subscriber = conflict.getSubscriber()
property = conflict.getPropertyId()
resolved = False
if property == 'description':
if subscriber.getSubscriptionUrlString() == self.publication_url:
resolved = True
property_id = conflict.getPropertyId()
if property_id == 'description' and \
subscriber.getUrlString() == self.publication_url:
conflict.applySubscriberValue()
if property == 'short_title':
if subscriber.getSubscriptionUrlString() == self.subscription_url['two_way']:
resolved = True
continue
if property_id == 'short_title' and \
subscriber.getUrlString() == self.subscription_url['two_way']:
conflict.applySubscriberValue()
if not resolved:
continue
conflict.applyPublisherValue()
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
self.assertEqual(document_c1.getDescription(), self.description2)
......@@ -849,10 +747,10 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
#XXX Error in convert XML
#self.assertEquals(self.size_filename_text, document_c1.get_size())
document_s = document_server._getOb(self.id1)
document_c = document_client1._getOb(self.id1)
self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c1,
ignore_processing_status_workflow=True)
@expectedFailure
def test_10_BrokenMessage(self):
"""
With http synchronization, when a message is not well
......@@ -862,15 +760,16 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
If we want to make this test more intersting, it is
better to split messages
"""
previous_max_lines = SynchronizationTool.MAX_LEN
previous_max_lines = SyncMLSubscription.MAX_LEN
try:
SynchronizationTool.MAX_LEN = 1 << 8
nb_document = self.createDocumentServerList()
# Synchronize the first client
nb_message1 = self.synchronizeWithBrokenMessage(self.sub_id1)
self.synchronizeWithBrokenMessage(self.sub_id1)
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync[self.sub_id1]
self.assertEqual(len(subscription1.getObjectList()), nb_document)
self.assertEqual(len(subscription1.getDocumentList()), nb_document)
document_server = self.getDocumentServer() # We also check we don't
# modify initial ob
document_s = document_server._getOb(self.id1)
......@@ -882,10 +781,10 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
self.assertEqual(document_s.getFilename(), self.filename_text)
self.assertEquals(self.size_filename_text, document_c.get_size())
self.assertXMLViewIsEqual(self.sub_id1, document_s, document_c)
SynchronizationTool.MAX_LEN = previous_max_lines
finally:
SyncMLSubscription.MAX_LEN = previous_max_lines
def addOneWaySyncFromServerSubscription(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id_from_server,
......@@ -897,7 +796,7 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
list_method_id='objectValues',
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id='ERP5DocumentConduit',
is_activity_enabled=True,
is_activity_enabled=self.activity_enable,
syncml_alert_code='one_way_from_server',
user_id='daniele',
password='myPassword')
......@@ -918,7 +817,7 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
sub_from_server = portal_sync[self.sub_id_from_server]
self.assertEquals(sub_from_server.getSyncmlAlertCode(), 'one_way_from_server')
self.assertEquals(nb_message1, self.nb_message_first_synchronization)
self.assertEquals(len(sub_from_server.getObjectList()), nb_document)
self.assertEquals(len(sub_from_server.getDocumentList()), nb_document)
document_server = self.getDocumentServer() # We also check we don't
# modify initial ob
document_s = document_server._getOb(self.id1)
......
......@@ -28,25 +28,24 @@
##############################################################################
import unittest
from Testing import ZopeTestCase
from Products.ERP5Type.tests.runUnitTest import tests_home
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from base64 import b64encode, b64decode, b16encode
from lxml import etree
from AccessControl.SecurityManagement import newSecurityManager
from zLOG import LOG
from base64 import b64encode, b64decode, b16encode, b16decode
from ERP5Diff import ERP5Diff
from lxml import etree
from Products.ERP5Type.tests.runUnitTest import tests_home
from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit
from Products.ERP5SyncML import SyncMLConstant
from Products.ERP5SyncML.XMLSyncUtils import encode, decode,\
isDecodeEncodeTheSame
from Products.ERP5SyncML.XMLSyncUtils import getConduitByName
from Products.ERP5SyncML.SyncMLConstant import MAX_LEN
from Products.ERP5SyncML.Tool import SynchronizationTool
from Products.ERP5SyncML.Document import SyncMLSubscription
from Products.ERP5SyncML.tests.testERP5SyncMLMixin import TestERP5SyncMLMixin \
as TestMixin
from Products.ERP5Type.tests.backportUnittest import expectedFailure
class TestERP5SyncMLMixin(ERP5TypeTestCase):
class TestERP5SyncMLMixin(TestMixin):
# Different variables used for this test
workflow_id = 'edit_workflow'
......@@ -82,23 +81,20 @@ class TestERP5SyncMLMixin(ERP5TypeTestCase):
nb_subscription = 2
nb_publication = 1
nb_synchronization = 3
nb_message_first_synchronization = 10
nb_message_first_sync_max_lines = 18
nb_message_first_synchronization = 6
nb_message_first_sync_max_lines = 16
_subscription_url1 = tests_home + '/sync_client1'
_subscription_url2 = tests_home + '/sync_client2'
_publication_url = tests_home + '/sync_server'
# XXX Why the prefix is not 'file://' ? This is inconsistent with urlopen:
# urlopen('file://tmp/foo') -> ERROR
# urlopen('file:///tmp/foo') -> OK
subscription_url1 = 'file:/' + _subscription_url1
subscription_url2 = 'file:/' + _subscription_url2
publication_url = 'file:/' + _publication_url
activity_enabled = False
#publication_url = 'server@localhost'
#subscription_url1 = 'client1@localhost'
#subscription_url2 = 'client2@localhost'
def getTitle(self):
return "ERP5SyncML Synchronous"
def afterSetUp(self):
"""Setup."""
self.login()
......@@ -109,32 +105,30 @@ class TestERP5SyncMLMixin(ERP5TypeTestCase):
def beforeTearDown(self):
"""Clean up."""
self.getTypesTool().getTypeInfo('Person').filter_content_types = 1
self.clearFiles()
self.getSynchronizationTool().manage_delObjects(
ids=list(self.getSynchronizationTool().objectIds()))
# clean modules
for module in ["person_server", "person_client1", "person_client2"]:
module = self.portal.get(module, None)
if module:
module.manage_delObjects(ids=list(module.objectIds()))
self.tic()
def getBusinessTemplateList(self):
"""
Return the list of business templates.
the business template sync_crm give 3 folders:
/person_server
/person_client1 : empty
/person_client2 : empty
"""
return ('erp5_base', 'erp5_syncml',)
def getSynchronizationTool(self):
return getattr(self.getPortal(), 'portal_synchronizations', None)
return ('erp5_core_proxy_field_legacy', 'erp5_base', 'erp5_syncml',)
def getPersonClient1(self):
return getattr(self.getPortal(), 'person_client1', None)
return getattr(self.getPortalObject(), 'person_client1', None)
def getPersonServer(self):
return getattr(self.getPortal(), 'person_server', None)
return getattr(self.getPortalObject(), 'person_server', None)
def getPersonClient2(self):
return getattr(self.getPortal(), 'person_client2', None)
def getPortalId(self):
return self.getPortal().getId()
return getattr(self.getPortalObject(), 'person_client2', None)
def login(self):
uf = self.getPortal().acl_users
......@@ -162,16 +156,15 @@ class TestERP5SyncMLMixin(ERP5TypeTestCase):
def populatePersonServer(self):
self.login()
portal = self.getPortal()
self.initPersonModule()
person_server = self.getPersonServer()
if getattr(person_server, self.id1, None) is None:
person1 = person_server.newContent(id=self.id1, portal_type='Person',
person_server.newContent(id=self.id1, portal_type='Person',
first_name=self.first_name1,
last_name=self.last_name1,
description=self.description1)
if getattr(person_server, self.id2, None) is None:
person2 = person_server.newContent(id=self.id2,
person_server.newContent(id=self.id2,
portal_type='Person',
first_name=self.first_name2,
last_name=self.last_name2,
......@@ -182,37 +175,21 @@ class TestERP5SyncMLMixin(ERP5TypeTestCase):
def populatePersonClient1(self):
self.login()
portal = self.getPortal()
self.initPersonModule()
person_client = self.getPersonClient1()
number_of_object = 60
for id in range(1, number_of_object+1):
person = person_client.newContent(portal_type='Person',
person_client.newContent(portal_type='Person',
id=id,
first_name=self.first_name1,
last_name=self.last_name1,
description=self.description1)
if id % 10 == 0:
# each 10 objects commit and reindex
self.tic()
nb_person = len(person_client)
self.assertEquals(nb_person, number_of_object)
return nb_person
def synchronize(self, id):
"""
This just define how we synchronize, we have
to define it here because it is specific to the unit testing
"""
portal_sync = self.getSynchronizationTool()
subscription = portal_sync[id]
publication = None
for pub in portal_sync.searchFolder(portal_type='SyncML Publication',
source_reference=subscription.getDestinationReference(),
validation_state='validated'):
if pub.getUrlString() == subscription.getUrlString():
publication = pub
self.assertTrue(publication is not None)
def clearFiles(self):
# reset files, because we do sync by files
file = open(self._subscription_url1, 'w')
file.write('')
......@@ -223,20 +200,34 @@ class TestERP5SyncMLMixin(ERP5TypeTestCase):
file = open(self._publication_url, 'w')
file.write('')
file.close()
def synchronize(self, id):
"""
This just define how we synchronize, we have
to define it here because it is specific to the unit testing
"""
portal_sync = self.getSynchronizationTool()
subscription = portal_sync[id]
pub_list = portal_sync.searchFolder(portal_type='SyncML Publication',
source_reference=subscription.getDestinationReference(),
validation_state='validated')
self.assertEqual(len(pub_list), 1)
publication = pub_list[0].getObject()
self.clearFiles()
nb_message = 1
result = portal_sync.SubSync(subscription.getPath())
result = portal_sync.processClientSynchronization(subscription.getPath())
# XXX-AUREL : in reality readResponse is called
# Why is it not call here ? This make the behaviour of
# the test rather different of what happens in real life !
while result['has_response']:
portal_sync.PubSync(publication.getPath())
if self.activity_enabled:
self.tic()
result = portal_sync.SubSync(subscription.getPath())
if self.activity_enabled:
while len(result) > 0:
result = portal_sync.processServerSynchronization(publication.getPath())
self.tic()
nb_message += 1 + result['has_response']
nb_message += 1
if len(result) == 0:
break
result = portal_sync.processClientSynchronization(subscription.getPath())
self.tic()
nb_message += 1
return nb_message
def synchronizeWithBrokenMessage(self, id):
......@@ -245,67 +236,108 @@ class TestERP5SyncMLMixin(ERP5TypeTestCase):
to define it here because it is specific to the unit testing
"""
portal_sync = self.getSynchronizationTool()
#portal_sync.email = None # XXX To be removed
subscription = portal_sync[id]
publication = None
for pub in portal_sync.searchFolder(portal_type='SyncML Publication',
pub_list = portal_sync.searchFolder(portal_type='SyncML Publication',
source_reference=subscription.getDestinationReference(),
validation_state='validated'):
if pub.getUrlString() == subscription.getUrlString():
publication = pub
self.assertTrue(publication is not None)
# reset files, because we do sync by files
file = open(self._subscription_url1, 'w')
file.write('')
file.close()
file = open(self._subscription_url2, 'w')
file.write('')
file.close()
file = open(self._publication_url, 'w')
file.write('')
file.close()
validation_state='validated')
self.assertEqual(len(pub_list), 1)
publication = pub_list[0].getObject()
self.clearFiles()
nb_message = 1
result = portal_sync.SubSync(subscription.getPath())
while result['has_response']:
result = portal_sync.processClientSynchronization(subscription.getPath())
while result:
# We do thing three times, so that we will test
# if we manage well duplicate messages
portal_sync.PubSync(publication.getPath())
if self.activity_enabled:
self.tic()
portal_sync.PubSync(publication.getPath())
if self.activity_enabled:
self.tic()
portal_sync.PubSync(publication.getPath())
if self.activity_enabled:
self.tic()
result = portal_sync.SubSync(subscription.getPath())
if self.activity_enabled:
# only first call will return an answer
result = portal_sync.processServerSynchronization(publication.getPath())
self.tic()
result = portal_sync.SubSync(subscription.getPath())
if self.activity_enabled:
for x in xrange(2):
portal_sync.processServerSynchronization(publication.getPath())
self.tic()
result = portal_sync.SubSync(subscription.getPath())
if self.activity_enabled:
nb_message += 1
if not len(result):
break
result = portal_sync.processClientSynchronization(subscription.getPath())
self.tic()
nb_message += 1 + result['has_response']
for x in xrange(2):
portal_sync.processClientSynchronization(subscription.getPath())
self.tic()
nb_message += 1
return nb_message
def getSynchronizationState(self, context):
"""
context : the context on which we are looking for state
This functions have to retrieve the synchronization state,
it will first look in the conflict list, if nothing is found,
then we have to check on a publication/subscription.
This method returns a mapping between subscription and states
JPS suggestion:
path -> object, document, context, etc.
type -> '/titi/toto' or ('','titi', 'toto') or <Base instance 1562567>
object = self.resolveContext(context) (method to add)
"""
if context is None or isinstance(context, tuple):
path = context
elif isinstance(context, basestring):
path = tuple(context.split('/'))
else:
path = context.getPhysicalPath()
portal_sync = self.getSynchronizationTool()
conflict_list = portal_sync.getConflictList()
state_list = []
append = state_list.append
for conflict in conflict_list:
if conflict.getOrigin() == path:
append([conflict.getSubscriber(), 'conflict'])
for domain in portal_sync.searchFolder():
destination = domain.getSource()
j_path = '/'.join(path)
if destination in j_path:
if domain.getPortalType() == 'SyncML Publication':
subscriber_list = [result.getObject() for result in\
domain.searchFolder(portal_type='SyncML Subscription')]
else:
subscriber_list = [domain,]
for subscriber in subscriber_list:
gid = subscriber.getGidFromObject(context)
signature = subscriber.getSignatureFromGid(gid)
#XXX check if signature could be not None ...
if signature is not None:
state = signature.getValidationState()
found = False
# Make sure there is not already a conflict giving the state
for state_item in state_list:
if state_item[0] == subscriber:
found = True
break
if not found:
append([subscriber, state])
return state_list
def checkSynchronizationStateIsSynchronized(self):
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
for person in person_server.objectValues():
state_list = portal_sync.getSynchronizationState(person)
state_list = self.getSynchronizationState(person)
for state in state_list:
self.assertEquals(state[1], 'synchronized')
person_client1 = self.getPersonClient1()
for person in person_client1.objectValues():
state_list = portal_sync.getSynchronizationState(person)
state_list = self.getSynchronizationState(person)
for state in state_list:
self.assertEquals(state[1], 'synchronized')
person_client2 = self.getPersonClient2()
for person in person_client2.objectValues():
state_list = portal_sync.getSynchronizationState(person)
state_list = self.getSynchronizationState(person)
for state in state_list:
self.assertEquals(state[1], 'synchronized')
# Check for each signature that the tempXML is None
......@@ -313,10 +345,12 @@ class TestERP5SyncMLMixin(ERP5TypeTestCase):
for m in sub.contentValues():
self.assertEquals(m.getTemporaryData(), None)
self.assertEquals(m.getPartialData(), None)
self.assertEquals(m.getValidationState(), "synchronized")
for pub in portal_sync.contentValues(portal_type='SyncML Publication'):
for sub in pub.contentValues(portal_type='SyncML Subscription'):
for m in sub.contentValues():
self.assertEquals(m.getPartialData(), None)
self.assertEquals(m.getValidationState(), "synchronized")
def verifyFirstNameAndLastNameAreNotSynchronized(self, first_name,
last_name, person_server, person_client):
......@@ -333,7 +367,7 @@ class TestERP5SyncMLMixin(ERP5TypeTestCase):
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync[self.sub_id1]
subscription2 = portal_sync[self.sub_id2]
self.assertEquals(len(subscription1.getObjectList()), nb_person)
self.assertEquals(len(subscription1.getDocumentList()), nb_person)
person_server = self.getPersonServer() # We also check we don't
# modify initial ob
person1_s = person_server._getOb(self.id1)
......@@ -345,7 +379,7 @@ class TestERP5SyncMLMixin(ERP5TypeTestCase):
self.assertEquals(person1_c.getId(), id)
self.assertEquals(person1_c.getFirstName(), self.first_name1)
self.assertEquals(person1_c.getLastName(), self.last_name1)
self.assertEquals(len(subscription2.getObjectList()), nb_person)
self.assertEquals(len(subscription2.getDocumentList()), nb_person)
person_client2 = self.getPersonClient2()
person2_c = person_client2._getOb(id)
self.assertEquals(person2_c.getId(), id)
......@@ -425,7 +459,6 @@ class TestERP5SyncML(TestERP5SyncMLMixin):
return "ERP5 SyncML"
def setupPublicationAndSubscription(self):
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
if person_server is not None:
portal = self.getPortal()
......@@ -446,28 +479,25 @@ class TestERP5SyncML(TestERP5SyncMLMixin):
sub1.setConduitModuleId('ERP5ConduitTitleGid')
sub2.setConduitModuleId('ERP5ConduitTitleGid')
pub.setConduitModuleId('ERP5ConduitTitleGid')
pub.setSynchronizationIdGeneratorMethodId('_generateNextId')
sub1.setSynchronizationIdGeneratorMethodId('_generateNextId')
sub2.setSynchronizationIdGeneratorMethodId('_generateNextId')
def checkSynchronizationStateIsConflict(self):
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
for person in person_server.objectValues():
if person.getId()==self.id1:
state_list = portal_sync.getSynchronizationState(person)
state_list = self.getSynchronizationState(person)
for state in state_list:
self.assertEquals(state[1], 'conflict')
person_client1 = self.getPersonClient1()
for person in person_client1.objectValues():
if person.getId()==self.id1:
state_list = portal_sync.getSynchronizationState(person)
state_list = self.getSynchronizationState(person)
for state in state_list:
self.assertEquals(state[1], 'conflict')
person_client2 = self.getPersonClient2()
for person in person_client2.objectValues():
if person.getId()==self.id1:
state_list = portal_sync.getSynchronizationState(person)
state_list = self.getSynchronizationState(person)
for state in state_list:
self.assertEquals(state[1], 'conflict')
# make sure sub object are also in a conflict mode
......@@ -475,7 +505,7 @@ class TestERP5SyncML(TestERP5SyncMLMixin):
# use a temp_object to create a no persistent object in person
sub_person =\
person.newContent(id=self.id1, portal_type='Person', temp_object=1)
state_list = portal_sync.getSynchronizationState(sub_person)
state_list = self.getSynchronizationState(sub_person)
for state in state_list:
self.assertEquals(state[1], 'conflict')
......@@ -546,14 +576,14 @@ class TestERP5SyncML(TestERP5SyncMLMixin):
#self.failUnless(self.getPersonClient2()!=None)
def addPublication(self):
portal_id = self.getPortalName()
portal_sync = self.getSynchronizationTool()
if getattr(portal_sync, self.pub_id, None) is not None:
portal_sync._delObject(self.pub_id)
self.tic()
publication = portal_sync.newContent(
portal_type='SyncML Publication',
id=self.pub_id,
url_string=self.publication_url,
url_string=self.publication_url, # XXX to be removed, maybe
source='person_server',
source_reference='Person',
list_method_id='objectValues',
......@@ -562,12 +592,13 @@ class TestERP5SyncML(TestERP5SyncMLMixin):
is_activity_enabled=self.activity_enabled)
publication.validate()
self.tic()
return publication
def addSubscription1(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
if getattr(portal_sync, self.sub_id1, None) is not None:
portal_sync._delObject(self.sub_id1)
self.tic()
subscription = portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id1,
url_string=self.publication_url,
......@@ -586,10 +617,10 @@ class TestERP5SyncML(TestERP5SyncMLMixin):
self.tic()
def addSubscription2(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
if getattr(portal_sync, self.sub_id2, None) is not None:
portal_sync._delObject(self.sub_id2)
self.tic()
subscription = portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id2,
url_string=self.publication_url,
......@@ -616,9 +647,9 @@ class TestERP5SyncML(TestERP5SyncMLMixin):
synchronization_list = portal_sync.objectValues()
self.assertEquals(len(synchronization_list), self.nb_synchronization)
def test_06_GetObjectList(self):
def test_06_getDocumentList(self):
"""
This test the default getObjectList, ie, when the
This test the default getDocumentList, ie, when the
query is 'objectValues', and this also test if we enter
a new method for the query
"""
......@@ -629,10 +660,10 @@ class TestERP5SyncML(TestERP5SyncMLMixin):
publication_list = portal_sync.contentValues(
portal_type='SyncML Publication')
publication = publication_list[0]
object_list = publication.getObjectList()
object_list = publication.getDocumentList()
self.assertEquals(len(object_list), nb_person)
# now try to set a different method for query
method_id = 'PersonServer_getObjectList'
method_id = 'PersonServer_getDocumentList'
## add test cached method
py_script_params = "**kw"
py_script_body = """
......@@ -643,12 +674,11 @@ return [context[%r]]
py_script_obj = getattr(self.portal, method_id)
py_script_obj.ZPythonScript_edit(py_script_params, py_script_body)
publication.setListMethodId(method_id)
object_list = publication.getObjectList()
object_list = publication.getDocumentList()
self.assertEquals(len(object_list), 1)
# Add the query path
portal_id = self.getPortalName()
publication.setListMethodId('person_server/objectValues')
object_list = publication.getObjectList()
object_list = publication.getDocumentList()
self.assertEquals(len(object_list), nb_person)
def test_07_ExportImport(self):
......@@ -709,7 +739,7 @@ return [context[%r]]
self.assertEquals(nb_message1, self.nb_message_first_synchronization)
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(len(subscription1.getObjectList()), nb_person)
self.assertEquals(len(subscription1.getDocumentList()), nb_person)
self.assertEquals(person1_s.getId(), self.id1)
self.assertEquals(person1_s.getFirstName(), long_line)
self.assertEquals(person1_s.getLastName(), self.last_name1)
......@@ -717,19 +747,6 @@ return [context[%r]]
person1_c = person_client1._getOb(self.id1)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
def test_10_GetObjectFromGid(self):
# We will try to get an object from a publication
# just by givin the gid
self.login()
self.setupPublicationAndSubscription()
self.populatePersonServer()
# By default we can just give the id
portal_sync = self.getSynchronizationTool()
publication = portal_sync[self.pub_id]
object = publication.getObjectFromId(self.id1)
self.failUnless(object is not None)
self.assertEquals(object.getId(), self.id1)
def test_11_GetSynchronizationState(self):
# We will try to get the state of objects
# that are just synchronized
......@@ -737,12 +754,12 @@ return [context[%r]]
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
state_list_s = portal_sync.getSynchronizationState(person1_s)
state_list_s = self.getSynchronizationState(person1_s)
self.assertEquals(len(state_list_s), self.nb_subscription) # one state
# for each subscriber
person_client1 = self.getPersonClient1()
person1_c = person_client1._getOb(self.id1)
state_list_c = portal_sync.getSynchronizationState(person1_c)
state_list_c = self.getSynchronizationState(person1_c)
self.assertEquals(len(state_list_c), 1) # one state
# for each subscriber
self.checkSynchronizationStateIsSynchronized()
......@@ -750,7 +767,6 @@ return [context[%r]]
def test_12_UpdateSimpleData(self):
self.test_08_FirstSynchronization()
# First we do only modification on server
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
kw = {'first_name':self.first_name3,'last_name':self.last_name3}
......@@ -807,7 +823,7 @@ return [context[%r]]
self.assertEquals(conflict.getLocalValue(), self.description2)
self.assertEquals(conflict.getRemoteValue(), self.description3)
subscriber = conflict.getSubscriber()
self.assertEquals(subscriber.getSubscriptionUrlString(), self.subscription_url1)
self.assertEquals(subscriber.getUrlString(), self.subscription_url1)
def test_14_GetPublisherAndSubscriberDocument(self):
# We will try to generate a conflict and then to get it
......@@ -815,10 +831,6 @@ return [context[%r]]
self.test_13_GetConflictList()
# First we do only modification on server
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
person_client1 = self.getPersonClient1()
person1_c = person_client1._getOb(self.id1)
conflict_list = portal_sync.getConflictList()
conflict = conflict_list[0]
publisher_document = conflict.getPublisherDocument()
......@@ -943,9 +955,9 @@ return [context[%r]]
publication = portal_sync[self.pub_id]
subscription1 = portal_sync[self.sub_id1]
subscription2 = portal_sync[self.sub_id2]
self.assertEquals(len(publication.getObjectList()), 0)
self.assertEquals(len(subscription1.getObjectList()), 0)
self.assertEquals(len(subscription2.getObjectList()), 0)
self.assertEquals(len(publication.getDocumentList()), 0)
self.assertEquals(len(subscription1.getDocumentList()), 0)
self.assertEquals(len(subscription2.getDocumentList()), 0)
def test_20_DeleteSubObject(self):
"""
......@@ -960,12 +972,15 @@ return [context[%r]]
- id2
"""
self.test_17_AddSubObject()
# Delete one on server
person_server = self.getPersonServer()
sub_object_s = person_server._getOb(self.id1)._getOb(self.id1)
sub_object_s.manage_delObjects(self.id1)
# Delete one on client 1
person_client1 = self.getPersonClient1()
sub_object_c1 = person_client1._getOb(self.id1)._getOb(self.id1)
sub_object_c1.manage_delObjects(self.id2)
# Do nothing on client 2
person_client2 = self.getPersonClient2()
sub_object_c2 = person_client2._getOb(self.id1)._getOb(self.id1)
self.synchronize(self.sub_id1)
......@@ -990,8 +1005,6 @@ return [context[%r]]
sub_object_s = object_s._getOb(self.id1)
person_client1 = self.getPersonClient1()
sub_object_c1 = person_client1._getOb(self.id1)._getOb(self.id1)
person_client2 = self.getPersonClient2()
sub_object_c2 = person_client2._getOb(self.id1)._getOb(self.id1)
# Change values so that we will get conflicts
kw = {'language':self.lang2,'description':self.description2}
sub_object_s.edit(**kw)
......@@ -1074,15 +1087,13 @@ return [context[%r]]
self.checkSynchronizationStateIsSynchronized()
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(len(subscription1.getObjectList()), nb_person)
self.assertEquals(len(subscription1.getDocumentList()), nb_person)
publication = portal_sync[self.pub_id]
self.assertEquals(len(publication.getObjectList()), nb_person)
self.assertEquals(len(publication.getDocumentList()), nb_person)
gid = self.first_name1 + ' ' + self.last_name1 # ie the title 'Sebastien Robin'
gid = b16encode(gid)
person_c1 = subscription1.getObjectFromGid(gid)
id_c1 = person_c1.getId()
self.failUnless(id_c1 in ('1','2')) # id given by the default generateNewId
person_s = publication.getSubscriber(self.subscription_url1).getObjectFromGid(gid)
person_c1 = subscription1.getDocumentFromGid(gid)
person_s = publication.getSubscriber(self.subscription_url1).getDocumentFromGid(gid)
id_s = person_s.getId()
self.assertEquals(id_s, self.id1)
# This will test updating object
......@@ -1094,14 +1105,13 @@ return [context[%r]]
self.assertXMLViewIsEqual(self.sub_id1, person_s, person_c1)
# This will test deleting object
person_server = self.getPersonServer()
person_client1 = self.getPersonClient1()
person_server.manage_delObjects(self.id2)
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
self.assertEquals(len(subscription1.getObjectList()), (nb_person-1))
self.assertEquals(len(publication.getObjectList()), (nb_person-1))
person_s = publication.getSubscriber(self.subscription_url1).getObjectFromGid(gid)
person_c1 = subscription1.getObjectFromGid(gid)
self.assertEquals(len(subscription1.getDocumentList()), (nb_person-1))
self.assertEquals(len(publication.getDocumentList()), (nb_person-1))
person_s = publication.getSubscriber(self.subscription_url1).getDocumentFromGid(gid)
person_c1 = subscription1.getDocumentFromGid(gid)
self.assertEquals(person_s.getDescription(), self.description3)
self.assertXMLViewIsEqual(self.sub_id1, person_s, person_c1)
......@@ -1143,11 +1153,11 @@ return [context[%r]]
property = conflict.getPropertyId()
resolve = 0
if property == 'language':
if subscriber.getSubscriptionUrlString() == self.subscription_url1:
if subscriber.getUrlString() == self.subscription_url1:
resolve = 1
conflict.applySubscriberValue()
if property == 'format':
if subscriber.getSubscriptionUrlString() == self.subscription_url2:
if subscriber.getUrlString() == self.subscription_url2:
resolve = 1
conflict.applySubscriberValue()
if not resolve:
......@@ -1177,7 +1187,6 @@ return [context[%r]]
# First, Create a new user
uf = self.getPortal().acl_users
uf._doAddUser('jp', '', ['Manager'], [])
user = uf.getUserById('jp').__of__(uf)
# then update create and delete roles
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
......@@ -1211,9 +1220,11 @@ return [context[%r]]
self.test_08_FirstSynchronization()
previous_max_lines = MAX_LEN
try:
SynchronizationTool.MAX_LEN = 1 << 8
SyncMLSubscription.MAX_LEN = 1 << 8
self.populatePersonServerWithSubObject()
self.checkSynchronizationStateIsSynchronized()
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
self.synchronize(self.sub_id2)
self.checkSynchronizationStateIsSynchronized()
person_client1 = self.getPersonClient1()
......@@ -1233,10 +1244,13 @@ return [context[%r]]
self.assertEquals(sub_sub_person2.getFirstName(),self.first_name2)
self.assertEquals(sub_sub_person2.getLastName(),self.last_name2)
finally:
SynchronizationTool.MAX_LEN = previous_max_lines
SyncMLSubscription.MAX_LEN = previous_max_lines
def test_29_BrokenMessage(self):
@expectedFailure
def test_29_SameMessageSentMultipleTime(self):
"""
XXX The way the synchronization is done make it loop forever
With http synchronization, when a message is not well
received, then we send message again, we want to
be sure that is such case we don't do stupid things
......@@ -1244,17 +1258,18 @@ return [context[%r]]
If we want to make this test more intersting, it is
better to split messages
"""
from Products.ERP5SyncML import SyncMLConstant
previous_max_lines = SyncMLConstant.MAX_LEN
SyncMLConstant.MAX_LEN = 1 << 4
try:
SyncMLConstant.MAX_LEN = 1 << 8
SyncMLSubscription.MAX_LEN = 1 << 8
self.setupPublicationAndSubscription()
nb_person = self.populatePersonServer()
# Synchronize the first client
nb_message1 = self.synchronizeWithBrokenMessage(self.sub_id1)
#self.failUnless(nb_message1==self.nb_message_first_synchronization)
self.synchronizeWithBrokenMessage(self.sub_id1)
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(len(subscription1.getObjectList()), nb_person)
self.assertEquals(len(subscription1.getDocumentList()), nb_person)
person_server = self.getPersonServer() # We also check we don't
# modify initial ob
person1_s = person_server._getOb(self.id1)
......@@ -1264,7 +1279,10 @@ return [context[%r]]
self.assertEquals(person1_s.getFirstName(), self.first_name1)
self.assertEquals(person1_s.getLastName(), self.last_name1)
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
finally:
SyncMLConstant.MAX_LEN = previous_max_lines
SyncMLSubscription.MAX_LEN =previous_max_lines
def test_30_GetSynchronizationType(self):
# We will try to update some simple data, first
......@@ -1357,7 +1375,6 @@ return [context[%r]]
self.assertXMLViewIsEqual(self.sub_id2, person2_s, person2_c)
def addOneWayFromServerSubscription(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
syncml_alert_code = self.portal.portal_categories.syncml_alert_code.\
one_way_from_server
......@@ -1381,8 +1398,6 @@ return [context[%r]]
password='myPassword')
self.tic()
self.assertTrue(subscription.isOneWayFromServer())
def test_33_OneWayFromServer(self):
"""
......@@ -1410,7 +1425,7 @@ return [context[%r]]
self.assertEquals(sub.getSyncmlAlertCode(), 'one_way_from_server')
self.assertEquals(nb_message1, self.nb_message_first_synchronization)
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(len(subscription1.getObjectList()), nb_person)
self.assertEquals(len(subscription1.getDocumentList()), nb_person)
person_server = self.getPersonServer() # We also check we don't
# modify initial ob
person1_s = person_server._getOb(self.id1)
......@@ -1448,7 +1463,6 @@ return [context[%r]]
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c, force=1)
def addOneWayFormClientSubscription(self):
portal_id = self.getPortalId()
portal_sync = self.getSynchronizationTool()
syncml_alert_code = self.portal.portal_categories.syncml_alert_code.\
one_way_from_client
......@@ -1492,7 +1506,7 @@ return [context[%r]]
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(subscription1.getSyncmlAlertCode(), 'one_way_from_client')
# First do the sync from the server to the client
nb_message1 = self.synchronize(self.sub_id1)
self.synchronize(self.sub_id1)
self.assertEquals(subscription1.getSyncmlAlertCode(),
'one_way_from_client')
#self.assertEquals(nb_message1, self.nb_message_first_synchronization)
......@@ -1509,34 +1523,29 @@ return [context[%r]]
self.checkSynchronizationStateIsSynchronized()
self.assertXMLViewIsEqual(self.sub_id1, client_person, server_person,
force=True)
# Then we change things on both sides and we look if there
# is synchronization only from only one client
# This will create a conflict !!
# Change part of the title on both side
# so that it generates a conflict
client_person.setFirstName(self.first_name2)
server_person.setLastName(self.last_name2)
nb_message1 = self.synchronize(self.sub_id1)
self.synchronize(self.sub_id1)
# refresh documents
server_person = server_person_module._getOb(object_id)
client_person = client_person_module._getOb(object_id)
# In One_From_Client Sync mode, first_name of object on server side
# doesn't change because no data is send from Subscription
# Conflict is generated on title
# But first name still is processed
# so first name must be up to date
self.assertEquals(server_person.getFirstName(), self.first_name2)
self.assertEquals(server_person.getLastName(), self.last_name2)
# Client get no change from server as it is in one way from client
self.assertEquals(client_person.getFirstName(), self.first_name2)
self.assertEquals(client_person.getLastName(), self.last_name1)
## Check again after conflict resolution
#self.assertEquals(server_person.getFirstName(), self.first_name2)
#self.assertEquals(server_person.getLastName(), self.last_name2)
#self.assertEquals(client_person.getFirstName(), self.first_name2)
#self.assertEquals(client_person.getLastName(), self.last_name1)
# reset for refresh sync
# after synchronize, the client object retrieve value of server
self.resetSignaturePublicationAndSubscription()
nb_message1 = self.synchronize(self.sub_id1)
self.synchronize(self.sub_id1)
# refresh documents
server_person = server_person_module._getOb(object_id)
......@@ -1544,10 +1553,126 @@ return [context[%r]]
self.assertEquals(server_person.getFirstName(), self.first_name2)
self.assertEquals(server_person.getLastName(), self.last_name1)
self.assertEquals(client_person.getFirstName(), self.first_name2)
self.assertEquals(client_person.getLastName(), self.last_name1)
self.checkSynchronizationStateIsSynchronized()
self.assertXMLViewIsEqual(self.sub_id1, client_person, server_person,
force=True)
def addRefreshFormClientOnlySubscription(self):
portal_sync = self.getSynchronizationTool()
syncml_alert_code = self.portal.portal_categories.syncml_alert_code.\
refresh_from_client_only
subscription = portal_sync._getOb(self.sub_id1, None)
if subscription is not None:
subscription.edit(syncml_alert_code_value=syncml_alert_code)
else:
subscription = portal_sync.newContent(portal_type='SyncML Subscription',
id=self.sub_id1,
url_string=self.publication_url,
subscription_url_string=self.subscription_url1,
source='person_client1',
source_reference='Person',
destination_reference='Person',
list_method_id='objectValues',
xml_binding_generator_method_id=self.xml_mapping,
conduit_module_id='ERP5Conduit',
is_activity_enabled=self.activity_enabled,
syncml_alert_code_value=syncml_alert_code,
user_id='fab',
password='myPassword')
self.tic()
def test_refreshFromClientOnly(self):
"""
Refresh from client only is used to send all data from the client to the
server, the server updating all its data. Modifications on server side are
not send to the client
"""
publication = self.addPublication()
self.addRefreshFormClientOnlySubscription()
nb_person = self.populatePersonClient1()
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(subscription1.getSyncmlAlertCode(),
'refresh_from_client_only')
# Execute first synchronization
# data from client will be synced on server
self.synchronize(self.sub_id1)
self.assertEquals(subscription1.getSyncmlAlertCode(),
'refresh_from_client_only')
# Check no signature created
self.assertEquals(len(subscription1), 0)
subscriber_list = publication.contentValues(portal_type="SyncML Subscription")
self.assertEquals(len(subscriber_list), 1)
subscriber = subscriber_list[0]
self.assertEquals(len(subscriber), 0)
# Check same person on client & server side
client_person_module = self.getPersonClient1()
server_person_module = self.getPersonServer()
for x in xrange(1, 61):
client_person = client_person_module._getOb(str(x))
server_person = server_person_module._getOb(str(x))
self.assertEquals(client_person.getFirstName(), self.first_name1)
self.assertEquals(client_person.getLastName(), self.last_name1)
self.assertEquals(server_person.getFirstName(), self.first_name1)
self.assertEquals(server_person.getLastName(), self.last_name1)
self.assertXMLViewIsEqual(self.sub_id1, client_person, server_person,
force=True)
# Modify data of persons on both side
client_person.setFirstName(self.first_name2)
server_person.setDescription(self.last_name2)
self.tic()
# Second synchronization
# Only client modification must have been synchronized
# Server modification will get ereased
self.synchronize(self.sub_id1)
# Check no signature created
self.assertEquals(len(subscription1), 0)
self.assertEquals(len(subscriber), 0)
for x in xrange(1, 61):
client_person = client_person_module._getOb(str(x))
server_person = server_person_module._getOb(str(x))
self.assertEquals(client_person.getFirstName(), self.first_name2)
self.assertEquals(client_person.getDescription(), self.description1)
self.assertEquals(server_person.getFirstName(), self.first_name2)
self.assertEquals(server_person.getDescription(), self.description1)
self.assertXMLViewIsEqual(self.sub_id1, client_person, server_person,
force=True)
# Modify same data of person on both side
client_person.setLastName(self.last_name2)
server_person.setLastName(self.first_name2)
# Third synchronization
# Client modifications must win over server modifications
self.synchronize(self.sub_id1)
# Check no signature created
self.assertEquals(len(subscription1), 0)
self.assertEquals(len(subscriber), 0)
for x in xrange(1, 61):
client_person = client_person_module._getOb(str(x))
server_person = server_person_module._getOb(str(x))
self.assertEquals(client_person.getLastName(), self.last_name2)
self.assertEquals(server_person.getLastName(), self.last_name2)
# These property should not have changed
self.assertEquals(client_person.getFirstName(), self.first_name2)
self.assertEquals(client_person.getDescription(), self.description1)
self.assertEquals(server_person.getFirstName(), self.first_name2)
self.assertEquals(server_person.getDescription(), self.description1)
self.assertXMLViewIsEqual(self.sub_id1, client_person, server_person,
force=True)
def test_34_encoding(self):
"""
We will test if we can encode strings with b64encode to encode
......@@ -1580,10 +1705,6 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
self.assertEquals(b64decode(awaited_result_long_string), long_string)
# test with the ERP5 functions
portal_sync = self.getSynchronizationTool()
publication = portal_sync[self.pub_id]
subscription1 = portal_sync[self.sub_id1]
string_encoded = encode('b64', python)
self.assertEquals(string_encoded, awaited_result_python)
string_decoded = decode('b64', awaited_result_python)
......@@ -1609,15 +1730,17 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
def test_35_authentication(self):
"""
we will test
- if we can't synchronize without good authentication for an
autentication required publication.
- if we can synchronize without of with (and bad or good) authentication
for an not required autentication publication
Check :
- synchronization failed if bad authentication
- authentication is case sensitive
- authentication work in utf-8
XXX Missing tests :
- no authentication provided on client
- sending authentication when not required
- empty password on client/server
"""
self.test_08_FirstSynchronization()
# First we do only modification on client
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
person_client1 = self.getPersonClient1()
......@@ -1626,73 +1749,72 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
kw = {'first_name':self.first_name3,'last_name':self.last_name3}
person1_c.edit(**kw)
#check that it's not synchronize
# check that it's not synchronize
self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name3,
self.last_name3, person1_s, person1_c)
self.synchronize(self.sub_id1)
#now it should be synchronize
# now it should be synchronize
self.checkSynchronizationStateIsSynchronized()
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
self.assertEquals(person1_s.getFirstName(), self.first_name3)
self.assertEquals(person1_s.getLastName(), self.last_name3)
#adding authentication :
# adding diferent authentication parameter on pub/sub
self.addAuthenticationToPublication(self.pub_id, 'fab', 'myPassword', 'b64',
'syncml:auth-basic')
self.addAuthenticationToSubscription(self.sub_id1, 'pouet', 'pouet',
'b64', 'syncml:auth-basic')
# try to synchronize with a wrong authentication on the subscription, it
# should failed
# Do some modification
kw = {'first_name':self.first_name2,'last_name':self.last_name2}
person1_c.edit(**kw)
self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name2,
self.last_name2, person1_s, person1_c)
# here, before and after synchronization, the person1_s shoudn't have
# the name as the person1_c because the user isn't authenticated
self.synchronize(self.sub_id1)
# try to synchronize with a wrong authentication on the subscription, it
# should failed
self.assertRaises(ValueError, self.synchronize, self.sub_id1)
self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name2,
self.last_name2, person1_s, person1_c)
#try to synchronize whith an authentication on both the client and server
# now make the authentication match
self.addAuthenticationToSubscription(self.sub_id1, 'fab', 'myPassword',
'b64', 'syncml:auth-basic')
#now it should be correctly synchronize
self.synchronize(self.sub_id1)
# data must now have been synchronized
self.checkSynchronizationStateIsSynchronized()
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
self.assertEquals(person1_s.getFirstName(), self.first_name2)
self.assertEquals(person1_s.getLastName(), self.last_name2)
#try to synchronize with a bad login and/or password
#test if login is case sensitive (it should be !)
# try to synchronize with a bad login and/or password
# test if login is case sensitive (it should be !)
self.addAuthenticationToSubscription(self.sub_id1, 'fAb', 'myPassword',
'b64', 'syncml:auth-basic')
kw = {'first_name':self.first_name1,'last_name':self.last_name1}
person1_c.edit(**kw)
self.synchronize(self.sub_id1)
self.assertRaises(ValueError, self.synchronize, self.sub_id1)
self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name1,
self.last_name1, person1_s, person1_c)
#with a paswword case sensitive
# with a paswword case sensitive
self.addAuthenticationToSubscription(self.sub_id1, 'fab', 'mypassword',
'b64', 'syncml:auth-basic')
kw = {'first_name':self.first_name1,'last_name':self.last_name1}
person1_c.edit(**kw)
self.synchronize(self.sub_id1)
self.assertRaises(ValueError, self.synchronize, self.sub_id1)
self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name1,
self.last_name1, person1_s, person1_c)
#with the good password
# with the good password
self.addAuthenticationToSubscription(self.sub_id1, 'fab', 'myPassword',
'b64', 'syncml:auth-basic')
#now it should be correctly synchronize
# now it should be correctly synchronize
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
self.assertXMLViewIsEqual(self.sub_id1, person1_s, person1_c)
self.assertEquals(person1_s.getFirstName(), self.first_name1)
self.assertEquals(person1_s.getLastName(), self.last_name1)
#verify that the login and password with utf8 caracters are accecpted
# verify that the login and password with utf8 caracters are accecpted
# add a user with an utf8 login
uf = self.getPortal().acl_users
......@@ -1702,17 +1824,17 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
self.addAuthenticationToPublication(self.pub_id, '\xc3\xa9pouet', 'ploum',
'b64', 'syncml:auth-basic')
#first, try with a wrong login :
# first, try with a wrong login :
self.addAuthenticationToSubscription(self.sub_id1, 'pouet', 'ploum',
'b64', 'syncml:auth-basic')
kw = {'first_name':self.first_name3,'last_name':self.last_name3}
person1_c.edit(**kw)
self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name3,
self.last_name3, person1_s, person1_c)
self.synchronize(self.sub_id1)
self.assertRaises(ValueError, self.synchronize, self.sub_id1)
self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name3,
self.last_name3, person1_s, person1_c)
#now with the good :
# now with the good :
self.addAuthenticationToSubscription(self.sub_id1, '\xc3\xa9pouet', 'ploum',
'b64', 'syncml:auth-basic')
self.synchronize(self.sub_id1)
......@@ -1721,10 +1843,13 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
self.assertEquals(person1_s.getLastName(), self.last_name3)
self.checkSynchronizationStateIsSynchronized()
@expectedFailure
def test_36_SynchronizationSubscriptionMaxLines(self):
# We will try to populate the folder person_server
# with the data form person_client
# with the data which over max line of messages
"""
XXX This has not been implemented in new syncml version
Check that messages are well splited when getting too many lines
"""
self.login()
self.setupPublicationAndSubscription()
nb_person = self.populatePersonClient1()
......@@ -1737,21 +1862,25 @@ wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA=="
#Verification number object synchronized
self.assertEquals(nb_message1, self.nb_message_first_sync_max_lines)
# Synchronize the second client
# data_Pub -> data_Sub2 the data are in pub to sub2 is empty so add +2 messages)
nb_message2 = self.synchronize(self.sub_id2)
self.assertEquals(nb_message2, self.nb_message_first_sync_max_lines + 2)
self.assertEquals(nb_message2, self.nb_message_first_sync_max_lines)
person_server = self.getPersonServer()
person_client1 = self.getPersonClient1()
person_client2 = self.getPersonClient2()
for id in range(1, 60):
person_s = person_server._getOb(str(id))
person_c = person_client1._getOb(str(id))
self.assertXMLViewIsEqual(self.sub_id1, person_s, person_c)
# Check we have all data synchronized
self.assertEquals(nb_person, len(person_server.objectValues()))
self.assertEquals(nb_person, len(person_client1.objectValues()))
self.assertEquals(nb_person, len(person_client2.objectValues()))
for id in range(1, 60):
person_s = person_server._getOb(str(id))
person_c1 = person_client1._getOb(str(id))
person_c2 = person_client2._getOb(str(id))
self.assertXMLViewIsEqual(self.sub_id1, person_s, person_c1)
self.assertXMLViewIsEqual(self.sub_id1, person_s, person_c2)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestERP5SyncML))
return suite
......@@ -31,26 +31,34 @@ from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
class TestERP5SyncMLMixin(ERP5TypeTestCase):
"""
Mixin classes that hold methods
Mixin class that hold methods
usefull to manage synchronizations
"""
def getSynchronizationTool(self):
"""
Return the tootl
"""
return self.portal.portal_synchronizations
def addSynchronizationUser(self, user_id="syncml", password="syncmlpass"):
"""
Add a user in acl_users that will be used for synchronization
"""
acl_users = self.portal.acl_users
if not acl_users.getUserById(user_id):
acl_users._doAddUser(user_id, password, ['Manager'], [])
return (user_id, password)
def updateSynchronizationURL(self, url=None):
def updateSynchronizationURL(self, url=None, object_list=None):
"""
Update the url defined on publication & subscription to the one
used by runUnitTest
"""
if not url:
url = self.portal.absolute_url()
for sync in self.portal.portal_synchronizations.objectValues():
if not object_list:
object_list = self.portal.portal_synchronizations.objectValues()
for sync in object_list:
if sync.getPortalType() == "SyncML Subscription":
sync.edit(url_string=url,
subscription_url_string=url,
......@@ -58,12 +66,14 @@ class TestERP5SyncMLMixin(ERP5TypeTestCase):
else:
sync.edit(url_string=url)
def updateAuthenticationCredentials(self, user_id, password):
def updateAuthenticationCredentials(self, user_id, password, object_list=None):
"""
Update subscripbtion to use specific authentication credentials
"""
for sync in self.portal.portal_synchronizations.objectValues(
portal_type="SyncML Subscription"):
if not object_list:
object_list = self.portal.portal_synchronizations.objectValues(
portal_type="SyncML Subscription")
for sync in object_list:
sync.edit(user_id=user_id,
password=password)
......
......@@ -29,10 +29,6 @@
##############################################################################
import os, sys
from AccessControl.SecurityManagement import newSecurityManager
from Products.ERP5SyncML.Conduit.VCardConduit import VCardConduit
from testERP5SyncML import TestERP5SyncMLMixin
from zLOG import LOG
......@@ -187,7 +183,7 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin):
self.assertEquals(person1_s.getLastName(), self.last_name3)
self.assertEquals(person1_c.getFirstName(), self.first_name3)
self.assertEquals(person1_c.getLastName(), self.last_name3)
nb_person_serv_before_sync = len(pub.getObjectList())
nb_person_serv_before_sync = len(pub.getDocumentList())
self.synchronize(self.sub_id1)
#after synchronization, no new person is created on server because it
#already have this person
......@@ -201,10 +197,10 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin):
self.assertEquals(person1_c.getFirstName(), self.first_name3)
self.assertEquals(person1_c.getLastName(), self.last_name3)
nb_person_serv_after_sync = len(pub.getObjectList())
nb_person_serv_after_sync = len(pub.getDocumentList())
#the number of person on server before and after the synchronization should
#be the same
nb_person_serv_after_sync = len(pub.getObjectList())
nb_person_serv_after_sync = len(pub.getDocumentList())
self.assertEquals(nb_person_serv_after_sync, nb_person_serv_before_sync)
......
......@@ -53,8 +53,8 @@ class AccountERP5IntegrationConduit(TioSafeBaseConduit):
"""
if object_id is None:
object_id = self.getAttribute(xml, 'id')
if object_id is not None:
if sub_object is None:
if True: # object_id is not None:
if sub_object is None and object_id:
try:
sub_object = object._getOb(object_id)
except (AttributeError, KeyError, TypeError):
......
......@@ -25,10 +25,9 @@
#
##############################################################################
from Products.CMFCore.WorkflowCore import WorkflowException
from Products.ERP5TioSafe.Conduit.TioSafeBaseConduit import TioSafeBaseConduit
from base64 import b16encode
from lxml import etree
from Products.ERP5TioSafe.Conduit.TioSafeBaseConduit import TioSafeBaseConduit
class AccountingERP5IntegrationConduit(TioSafeBaseConduit):
"""
......@@ -104,8 +103,8 @@ class AccountingERP5IntegrationConduit(TioSafeBaseConduit):
"""
if object_id is None:
object_id = self.getAttribute(xml, 'id')
if object_id is not None:
if sub_object is None:
if True: # object_id is not None:
if sub_object is None and object_id:
sub_object = object._getOb(object_id, None)
if sub_object is None: # If so, it does not exist
portal_type = ''
......@@ -241,7 +240,7 @@ class AccountingERP5IntegrationConduit(TioSafeBaseConduit):
subscriber = arrow_dict[tag]['sync']
# Encode to the output type
link_gid = subnode.text
link_object = subscriber.getObjectFromGid(b16encode(link_gid))
link_object = subscriber.getDocumentFromGid(b16encode(link_gid))
else:
link_object = None
......
......@@ -149,7 +149,7 @@ class ERP5NodeConduit(TioSafeBaseConduit):
document.setCareerSubordinationValue(None)
else:
for synchronization in synchronization_list:
link_object = synchronization.getObjectFromGid(b16encode(organisation_gid))
link_object = synchronization.getDocumentFromGid(b16encode(organisation_gid))
if link_object is not None:
break
if link_object is not None:
......@@ -164,7 +164,7 @@ class ERP5NodeConduit(TioSafeBaseConduit):
""" This is the method calling to create an object. """
# if DEBUG:
# LOG("ERP5NodeContuide._createContent", INFO, "xml = %s" %(etree.tostring(xml, pretty_print=True),))
if object_id is not None:
if True: # object_id is not None:
sub_object = None
if sub_object is None: # If so, it doesn't exist
# Check if we can find it in module
......@@ -288,7 +288,6 @@ class ERP5NodeConduit(TioSafeBaseConduit):
reset_workflow=reset_workflow,
)
self.afterCreateMethod(sub_object, **kw)
return sub_object
......
......@@ -102,8 +102,8 @@ class ERP5PaymentTransactionConduit(TioSafeBaseConduit):
"""
if object_id is None:
object_id = self.getAttribute(xml, 'id')
if object_id is not None:
if sub_object is None:
if True: #object_id is not None:
if sub_object is None and object_id:
try:
sub_object = object._getOb(object_id)
except (AttributeError, KeyError, TypeError):
......
......@@ -51,7 +51,7 @@ class ERP5ResourceConduit(TioSafeBaseConduit):
"""
This is the method calling to create an object
"""
if object_id is not None:
if True: # object_id is not None:
if sub_object is None:
sub_object = object._getOb(object_id, None)
if sub_object is None: # If so, it does not exist
......
......@@ -26,12 +26,11 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from base64 import b16encode
from copy import deepcopy
from Products.ERP5TioSafe.Conduit.TioSafeBaseConduit import TioSafeBaseConduit
from Products.ERP5SyncML.SyncMLConstant import XUPDATE_INSERT_OR_ADD_LIST
from base64 import b16encode
from zLOG import LOG, WARNING
from copy import deepcopy
class ERP5TransactionConduit(TioSafeBaseConduit):
"""
......@@ -61,8 +60,8 @@ class ERP5TransactionConduit(TioSafeBaseConduit):
"""
if object_id is None:
object_id = self.getAttribute(xml, 'id')
if object_id is not None:
if sub_object is None:
if True: # object_id is not None:
if sub_object is None and object_id:
try:
sub_object = object._getOb(object_id)
except (AttributeError, KeyError, TypeError):
......@@ -236,8 +235,8 @@ class ERP5TransactionConduit(TioSafeBaseConduit):
else:
for synchronization in synchronization_list:
# encode to the output type
if getattr(synchronization, 'getObjectFromGid', None) is not None:
link_object = synchronization.getObjectFromGid(b16encode(link_gid))
if getattr(synchronization, 'getDocumentFromGid', None) is not None:
link_object = synchronization.getDocumentFromGid(b16encode(link_gid))
#LOG("trying to get %s from %s, got %s" %(link_gid, synchronization.getPath(), link_object), 300, "This is for category type %s" %(category))
if link_object is not None:
break
......@@ -286,7 +285,7 @@ class ERP5TransactionConduit(TioSafeBaseConduit):
else:
synchronization_list = self.getSynchronizationObjectListForType(kw.get('domain'), 'Product', 'publication')
for synchronization in synchronization_list:
link_object = synchronization.getObjectFromGid(b16encode(link_gid))
link_object = synchronization.getDocumentFromGid(b16encode(link_gid))
if link_object is not None:
break
# in the worse case save the line with the unknown product
......@@ -307,7 +306,7 @@ class ERP5TransactionConduit(TioSafeBaseConduit):
# set line values in the dict
if subnode.text is not None:
movement_dict_value[tag] = subnode.text#.encode('utf-8')
LOG("visitMovement", 300, "movement_dict_value = %s" %(movement_dict_value))
#LOG("visitMovement", 300, "movement_dict_value = %s" %(movement_dict_value))
if 'quantity' not in movement_dict_value:
return
......
......@@ -51,7 +51,9 @@ class TioSafeBaseConduit(ERP5Conduit):
XXX name of method is not good, because content is not necessarily XML
return a xml with id replaced by a new id
"""
if isinstance(xml, str) or isinstance(xml, unicode):
if isinstance(xml, unicode):
xml = xml.encode("utf-8")
if isinstance(xml, basestring):
xml = etree.XML(str(xml), parser=parser)
else:
# copy of xml object for modification
......@@ -106,7 +108,7 @@ class TioSafeBaseConduit(ERP5Conduit):
return {'conflict_list': conflict_list, 'object': sub_object}
# In the case where this new node is a object to add
xpath_expression = xml.get('select')
if xml.xpath('name()') in XUPDATE_INSERT_OR_ADD_LIST and\
if xml.xpath('name()') in XUPDATE_INSERT_OR_ADD_LIST and \
MARSHALLER_NAMESPACE_URI not in xml.nsmap.values():
# change the context according select expression
get_target_parent = xml.xpath('name()') in XUPDATE_INSERT_LIST
......@@ -133,7 +135,9 @@ class TioSafeBaseConduit(ERP5Conduit):
def applyXupdate(self, object=None, object_xml=None, xupdate=None, previous_xml=None, **kw):
""" Parse the xupdate and then it will call the conduit. """
conflict_list = []
if isinstance(xupdate, (str, unicode)):
if isinstance(xupdate, unicode):
xupdate = xupdate.encode("utf-8")
if isinstance(xupdate, basestring):
xupdate = etree.XML(xupdate, parser=parser)
if kw.get('conduit', None) is not None:
for subnode in xupdate:
......@@ -151,6 +155,9 @@ class TioSafeBaseConduit(ERP5Conduit):
Return the integration site based on the link with the pub/sub
"""
if getattr(self, 'integration_site', None) is None:
if sync_object.getParentValue().getPortalType() == 'SyncML Publication':
related_object_list = [x.getObject() for x in sync_object.getParentValue().Base_getRelatedObjectList()]
else:
related_object_list = [x.getObject() for x in sync_object.Base_getRelatedObjectList()]
if len(related_object_list) != 1:
raise ValueError, "Impossible to find related object to %s : %s" %(sync_object.getPath(), related_object_list)
......
......@@ -188,7 +188,6 @@ class FolderMixIn(ExtensionClass.Base):
my_id = self._generateRandomId()
return "%s-%s" %(current_date, my_id)
def _generateRandomId(self):
"""
Generate a random Id.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment