Commit a270c222 authored by Aurel's avatar Aurel

Implement support of deletion detection by ERP5SyncML & improve workflow usage on signatures

parent badb7a60
<?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/boolean</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Indicate if it check deleted object at the end or with the splitted activities that check data changes</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>preferred_check_delete_at_end_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: True</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -2,38 +2,37 @@ ...@@ -2,38 +2,37 @@
<ZopeData> <ZopeData>
<record id="1" aka="AAAAAAAAAAE="> <record id="1" aka="AAAAAAAAAAE=">
<pickle> <pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/> <global name="Standard Property" module="erp5.portal_type"/>
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/boolean</string>
</tuple>
</value>
</item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
<value> <string></string> </value> <value> <string>Define if indexation of source data will be splitted in activity or not</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>not_synchronized</string> </value> <value> <string>preferred_split_indexation_property</string> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>portal_type</string> </key>
<value> <string>Not Synchronized</string> </value> <value> <string>Standard Property</string> </value>
</item> </item>
<item> <item>
<key> <string>transitions</string> </key> <key> <string>preference</string> </key>
<value> <value> <int>1</int> </value>
<tuple>
<string>change_to_conflict</string>
<string>change_to_partial</string>
<string>do_sync</string>
<string>synchronize</string>
</tuple>
</value>
</item> </item>
<item> <item>
<key> <string>type_list</string> </key> <key> <string>property_default</string> </key>
<value> <value> <string>python: False</string> </value>
<tuple/>
</value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -50,39 +50,48 @@ ...@@ -50,39 +50,48 @@
</item> </item>
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>signature = state_change[\'object\']\n <value> <string>if not len(path_list):\n
return\n
restrictedTraverse = context.getPortalObject().restrictedTraverse\n
argument_getter_dict = {}\n
if subscription_path:\n
subscription = restrictedTraverse(subscription_path)\n
getId = subscription.getGidFromObject\n
getData = subscription.getDataFromDocument\n
else:\n
getId = getData = None\n
\n \n
edit_kw = {}\n method = context.z_catalog_syncml_document_list\n
\n \n
temporary_data = signature.getTemporaryData()\n parameter_append_list = []\n
if temporary_data is not None:\n append = parameter_append_list.append\n
# This happens when we have sent the xml\n parameter_dict = {}\n
# and we just get the confirmation\n for property in method.arguments_src.split():\n
signature.setData(temporary_data)\n parameter_dict[property] = parameter_value_list = []\n
edit_kw["temporary_data"] = None\n if property == \'getData\':\n
getter = getData\n
elif property == \'getId\':\n
getter = getId\n
else:\n
getter = None\n
if getter is None:\n
getter = lambda obj, property=property: getattr(obj, property)()\n
append((parameter_value_list, getter))\n
\n \n
if signature.isForce():\n for path in path_list:\n
edit_kw["force"] = False\n obj = restrictedTraverse(path)\n
if signature.hasPartialData():\n for value_list, getter in parameter_append_list:\n
edit_kw["partial_data"] = None\n value_list.append(getter(obj))\n
if signature.hasSubscriberXupdate():\n method(**parameter_dict)\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
context.SyncMLSignature_resetConflictList(state_change)\n
</string> </value> </string> </value>
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>state_change</string> </value> <value> <string>path_list, subscription_path=None, activate_kw=None</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>SyncMLSignature_afterSynchronize</string> </value> <value> <string>SQLCatalog_indexSyncMLDocumentList</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -89,6 +89,8 @@ ...@@ -89,6 +89,8 @@
<value> <value>
<list> <list>
<string>my_preferred_sync_action_per_activity_count</string> <string>my_preferred_sync_action_per_activity_count</string>
<string>my_preferred_check_delete_at_end</string>
<string>my_preferred_split_indexation</string>
</list> </list>
</value> </value>
</item> </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_preferred_check_delete_at_end</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_checkbox</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>Check Deletion at End</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>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_preferred_split_indexation</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_checkbox</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>Split Source Indexation</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>getId\r\n
getPath\r\n
getData\r\n
</string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_catalog_syncml_document_list</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
REPLACE INTO\n
syncml (`path`, `gid`, `data`)\n
VALUES\n
<dtml-in prefix="loop" expr="_.range(_.len(getPath))">\n
(\n
<dtml-sqlvar expr="getPath[loop_item]" type="string">,\n
<dtml-sqlvar expr="getId[loop_item]" type="string">,\n
<dtml-sqlvar expr="getData[loop_item]" type="string">\n
)<dtml-unless sequence-end>,</dtml-unless>\n
</dtml-in>\n
]]></string> </value>
</item>
<item>
<key> <string>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="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_col</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_create_syncml</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string>CREATE TABLE `syncml` (\n
`path` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT \'\',\n
`gid` varchar(255) COLLATE utf8_unicode_ci DEFAULT \'\',\n
`data` LONGBLOB NULL,\n
PRIMARY KEY (`path`),\n
KEY `gid` (`gid`,`path`)\n
) ENGINE=InnoDB;\n
</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -2,37 +2,36 @@ ...@@ -2,37 +2,36 @@
<ZopeData> <ZopeData>
<record id="1" aka="AAAAAAAAAAE="> <record id="1" aka="AAAAAAAAAAE=">
<pickle> <pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/> <global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item> <item>
<key> <string>description</string> </key> <key> <string>arguments_src</string> </key>
<value> <string></string> </value> <value> <string>path</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>connection_id</string> </key>
<value> <string>partial</string> </value> <value> <string>erp5_sql_connection</string> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>id</string> </key>
<value> <string>Partial</string> </value> <value> <string>z_delete_data_from_path</string> </value>
</item> </item>
<item> <item>
<key> <string>transitions</string> </key> <key> <string>src</string> </key>
<value> <value> <string encoding="cdata"><![CDATA[
<tuple>
<string>change_to_conflict</string> DELETE FROM syncml\n
<string>do_sync</string> WHERE\n
<string>synchronize</string> path = <dtml-sqlvar path type="string">\n
</tuple>
</value>
]]></string> </value>
</item> </item>
<item> <item>
<key> <string>type_list</string> </key> <key> <string>title</string> </key>
<value> <value> <string></string> </value>
<tuple/>
</value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -2,36 +2,35 @@ ...@@ -2,36 +2,35 @@
<ZopeData> <ZopeData>
<record id="1" aka="AAAAAAAAAAE="> <record id="1" aka="AAAAAAAAAAE=">
<pickle> <pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/> <global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item> <item>
<key> <string>description</string> </key> <key> <string>_col</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>connection_id</string> </key>
<value> <string>synchronized</string> </value> <value> <string>erp5_sql_connection</string> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>id</string> </key>
<value> <string>Synchronized</string> </value> <value> <string>z_drop_syncml</string> </value>
</item> </item>
<item> <item>
<key> <string>transitions</string> </key> <key> <string>src</string> </key>
<value> <value> <string>DROP TABLE IF EXISTS syncml</string> </value>
<tuple>
<string>change_to_conflict</string>
<string>drift</string>
</tuple>
</value>
</item> </item>
<item> <item>
<key> <string>type_list</string> </key> <key> <string>title</string> </key>
<value> <value> <string></string> </value>
<tuple/>
</value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>signature_path\r\n
source_path</string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_get_syncml_deleted_gid_list</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
SELECT gid \n
FROM syncml\n
WHERE\n
path like <dtml-sqlvar signature_path type="string">\n
AND\n
gid not in (SELECT gid FROM syncml WHERE\n
path like <dtml-sqlvar source_path type="string">)
]]></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -2,55 +2,85 @@ ...@@ -2,55 +2,85 @@
<ZopeData> <ZopeData>
<record id="1" aka="AAAAAAAAAAE="> <record id="1" aka="AAAAAAAAAAE=">
<pickle> <pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/> <global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item> <item>
<key> <string>actbox_category</string> </key> <key> <string>allow_simple_one_argument_traversal</string> </key>
<value> <string>workflow</string> </value> <value>
<none/>
</value>
</item> </item>
<item> <item>
<key> <string>actbox_name</string> </key> <key> <string>arguments_src</string> </key>
<value> <string></string> </value> <value> <string>strict_min_gid\r\n
min_gid\r\n
max_gid\r\n
path\r\n
limit</string> </value>
</item> </item>
<item> <item>
<key> <string>actbox_url</string> </key> <key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>after_script_name</string> </key> <key> <string>class_name_</string> </key>
<value> <string>SyncMLSignature_afterSynchronize</string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>description</string> </key> <key> <string>connection_hook</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>guard</string> </key> <key> <string>connection_id</string> </key>
<value> <value> <string>erp5_sql_connection</string> </value>
<none/>
</value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>synchronize</string> </value> <value> <string>z_get_syncml_gid_list</string> </value>
</item> </item>
<item> <item>
<key> <string>new_state_id</string> </key> <key> <string>max_cache_</string> </key>
<value> <string>synchronized</string> </value> <value> <int>100</int> </value>
</item> </item>
<item> <item>
<key> <string>script_name</string> </key> <key> <string>max_rows_</string> </key>
<value> <string></string> </value> <value> <int>0</int> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>src</string> </key>
<value> <string></string> </value> <value> <string encoding="cdata"><![CDATA[
SELECT \n
gid\n
FROM syncml\n
WHERE\n
path like <dtml-sqlvar path type="string">\n
<dtml-if strict_min_gid>\n
AND gid > <dtml-sqlvar strict_min_gid type="string">\n
</dtml-if>\n
<dtml-if min_gid>\n
AND gid >= <dtml-sqlvar min_gid type="string">\n
</dtml-if>\n
<dtml-if max_gid>\n
AND gid <= <dtml-sqlvar max_gid type="string">\n
</dtml-if>\n
ORDER BY gid\n
<dtml-if limit>\n
LIMIT <dtml-var limit>\n
</dtml-if>\n
]]></string> </value>
</item> </item>
<item> <item>
<key> <string>trigger_type</string> </key> <key> <string>title</string> </key>
<value> <int>2</int> </value> <value> <string></string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -2,55 +2,75 @@ ...@@ -2,55 +2,75 @@
<ZopeData> <ZopeData>
<record id="1" aka="AAAAAAAAAAE="> <record id="1" aka="AAAAAAAAAAE=">
<pickle> <pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/> <global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item> <item>
<key> <string>actbox_category</string> </key> <key> <string>allow_simple_one_argument_traversal</string> </key>
<value> <string>workflow</string> </value> <value>
<none/>
</value>
</item> </item>
<item> <item>
<key> <string>actbox_name</string> </key> <key> <string>arguments_src</string> </key>
<value> <string></string> </value> <value> <string>min_gid\r\n
max_gid\r\n
path</string> </value>
</item> </item>
<item> <item>
<key> <string>actbox_url</string> </key> <key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>after_script_name</string> </key> <key> <string>class_name_</string> </key>
<value> <string>SyncMLSignature_afterDrift</string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>description</string> </key> <key> <string>connection_hook</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>guard</string> </key> <key> <string>connection_id</string> </key>
<value> <value> <string>erp5_sql_connection</string> </value>
<none/>
</value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>drift</string> </value> <value> <string>z_get_syncml_path_list</string> </value>
</item> </item>
<item> <item>
<key> <string>new_state_id</string> </key> <key> <string>max_cache_</string> </key>
<value> <string>not_synchronized</string> </value> <value> <int>100</int> </value>
</item> </item>
<item> <item>
<key> <string>script_name</string> </key> <key> <string>max_rows_</string> </key>
<value> <string></string> </value> <value> <int>0</int> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>src</string> </key>
<value> <string></string> </value> <value> <string encoding="cdata"><![CDATA[
SELECT path, gid, data \n
FROM syncml\n
WHERE\n
path like <dtml-sqlvar path type="string">\n
<dtml-if min_gid>\n
AND gid >= <dtml-sqlvar min_gid type="string">\n
</dtml-if>\n
<dtml-if max_gid>\n
AND gid <= <dtml-sqlvar max_gid type="string">\n
</dtml-if>\n
]]></string> </value>
</item> </item>
<item> <item>
<key> <string>trigger_type</string> </key> <key> <string>title</string> </key>
<value> <int>2</int> </value> <value> <string></string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>path</string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_unindex_syncml_data</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
DELETE FROM syncml\n
WHERE\n
path like <dtml-sqlvar path type="string">\n
]]></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
</item> </item>
<item> <item>
<key> <string>initial_state</string> </key> <key> <string>initial_state</string> </key>
<value> <string>not_synchronized</string> </value> <value> <string>no_conflict</string> </value>
</item> </item>
<item> <item>
<key> <string>manager_bypass</string> </key> <key> <string>manager_bypass</string> </key>
......
<?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>signature = state_change[\'object\']\n
if signature.hasPartialData():\n
signature.setPartialData(None)\n
if signature.hasTemporaryData():\n
signature.setTemporaryData(None)\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SyncMLSignature_afterDrift</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>signature = state_change[\'object\']\n
signature.resetConflictList()\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SyncMLSignature_resetConflictList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -22,10 +22,9 @@ ...@@ -22,10 +22,9 @@
<key> <string>transitions</string> </key> <key> <string>transitions</string> </key>
<value> <value>
<tuple> <tuple>
<string>drift</string> <string>no_conflict</string>
<string>resolve_conflict_with_client_command_winning</string> <string>resolve_conflict_with_client_command_winning</string>
<string>resolve_conflict_with_merge</string> <string>resolve_conflict_with_merge</string>
<string>synchronize</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
<tuple> <tuple>
<string>change_to_partial</string> <string>change_to_partial</string>
<string>do_sync</string> <string>do_sync</string>
<string>no_conflict</string>
<string>synchronize</string> <string>synchronize</string>
</tuple> </tuple>
</value> </value>
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
<tuple> <tuple>
<string>change_to_partial</string> <string>change_to_partial</string>
<string>do_sync</string> <string>do_sync</string>
<string>no_conflict</string>
<string>synchronize</string> <string>synchronize</string>
</tuple> </tuple>
</value> </value>
......
...@@ -8,22 +8,21 @@ ...@@ -8,22 +8,21 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
<value> <string></string> </value> <value> <string>Default state of signature</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>syncing</string> </value> <value> <string>no_conflict</string> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>Syncing</string> </value> <value> <string>No Conflict</string> </value>
</item> </item>
<item> <item>
<key> <string>transitions</string> </key> <key> <string>transitions</string> </key>
<value> <value>
<tuple> <tuple>
<string>change_to_conflict</string> <string>change_to_conflict</string>
<string>synchronize</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
</item> </item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
<value> <string></string> </value> <value> <string>Reset to default state</string> </value>
</item> </item>
<item> <item>
<key> <string>guard</string> </key> <key> <string>guard</string> </key>
...@@ -38,11 +38,11 @@ ...@@ -38,11 +38,11 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>do_sync</string> </value> <value> <string>no_conflict</string> </value>
</item> </item>
<item> <item>
<key> <string>new_state_id</string> </key> <key> <string>new_state_id</string> </key>
<value> <string>syncing</string> </value> <value> <string>no_conflict</string> </value>
</item> </item>
<item> <item>
<key> <string>script_name</string> </key> <key> <string>script_name</string> </key>
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string></string> </value> <value> <string>No Conflict</string> </value>
</item> </item>
<item> <item>
<key> <string>trigger_type</string> </key> <key> <string>trigger_type</string> </key>
......
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
<key> <string>actbox_category</string> </key> <key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value> <value> <string>workflow</string> </value>
</item> </item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item> <item>
<key> <string>actbox_name</string> </key> <key> <string>actbox_name</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
...@@ -20,7 +24,7 @@ ...@@ -20,7 +24,7 @@
</item> </item>
<item> <item>
<key> <string>after_script_name</string> </key> <key> <string>after_script_name</string> </key>
<value> <string>SyncMLSignature_resetConflictList</string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
......
94 118
\ No newline at end of file
...@@ -42,6 +42,8 @@ class testSyncMLAsynchronousEngine(TestERP5SyncMLMixin): ...@@ -42,6 +42,8 @@ class testSyncMLAsynchronousEngine(TestERP5SyncMLMixin):
This is ran before anything, used to set the environment This is ran before anything, used to set the environment
""" """
self.sync_tool = self.portal.portal_synchronizations self.sync_tool = self.portal.portal_synchronizations
self.portal.z_drop_syncml()
self.portal.z_create_syncml()
# here, you can create the categories and objects your test will depend on # here, you can create the categories and objects your test will depend on
def _initSyncModule(self): def _initSyncModule(self):
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
<item>source_section</item> <item>source_section</item>
</portal_type> </portal_type>
<portal_type id="Integration Site"> <portal_type id="Integration Site">
<item>resource</item>
<item>destination_payment</item> <item>destination_payment</item>
<item>resource</item>
<item>source_account</item> <item>source_account</item>
<item>source_payment</item> <item>source_payment</item>
</portal_type> </portal_type>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<portal_type id="Integration Module"> <portal_type id="Integration Module">
<item>IntegrationModule</item> <item>IntegrationModule</item>
</portal_type> </portal_type>
<portal_type id="Integration Site"> <portal_type id="Integration Site">
<item>Arrow</item> <item>Arrow</item>
<item>Resource</item> <item>Resource</item>
</portal_type> </portal_type>
......
...@@ -28,10 +28,6 @@ ...@@ -28,10 +28,6 @@
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>SynchronizationConfiguratorItem</string> </value> <value> <string>SynchronizationConfiguratorItem</string> </value>
</item> </item>
<item>
<key> <string>last_id</string> </key>
<value> <string>2</string> </value>
</item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value> <value> <string>Property Sheet</string> </value>
......
...@@ -28,10 +28,6 @@ ...@@ -28,10 +28,6 @@
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>WebServiceConnector</string> </value> <value> <string>WebServiceConnector</string> </value>
</item> </item>
<item>
<key> <string>last_id</string> </key>
<value> <string>1</string> </value>
</item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value> <value> <string>Property Sheet</string> </value>
......
...@@ -28,10 +28,6 @@ ...@@ -28,10 +28,6 @@
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>WebServiceRequest</string> </value> <value> <string>WebServiceRequest</string> </value>
</item> </item>
<item>
<key> <string>last_id</string> </key>
<value> <string>11</string> </value>
</item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value> <value> <string>Property Sheet</string> </value>
......
...@@ -67,75 +67,14 @@ if account.getReference() and account.getValidationState() != \'deleted\':\n ...@@ -67,75 +67,14 @@ if account.getReference() and account.getValidationState() != \'deleted\':\n
return account_list\n return account_list\n
</string> </value> </string> </value>
</item> </item>
<item>
<key> <string>_code</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>id="", gid=""</string> </value> <value> <string>id="", gid=""</string> </value>
</item> </item>
<item>
<key> <string>errors</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>func_code</string> </key>
<value>
<object>
<klass>
<global name="FuncCode" module="Shared.DC.Scripts.Signature"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>co_argcount</string> </key>
<value> <int>2</int> </value>
</item>
<item>
<key> <string>co_varnames</string> </key>
<value>
<tuple>
<string>id</string>
<string>gid</string>
<string>account_list</string>
<string>_getiter_</string>
<string>_getattr_</string>
<string>context</string>
<string>account</string>
<string>getattr</string>
</tuple>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>func_defaults</string> </key>
<value>
<tuple>
<string></string>
<string></string>
</tuple>
</value>
</item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Account_getAccountValueList</string> </value> <value> <string>Account_getAccountValueList</string> </value>
</item> </item>
<item>
<key> <string>warnings</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
......
...@@ -67,75 +67,14 @@ if accounting.getReference() and accounting.getStartDate() and accounting.getSim ...@@ -67,75 +67,14 @@ if accounting.getReference() and accounting.getStartDate() and accounting.getSim
return accounting_list\n return accounting_list\n
</string> </value> </string> </value>
</item> </item>
<item>
<key> <string>_code</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>id="", gid=""</string> </value> <value> <string>id="", gid=""</string> </value>
</item> </item>
<item>
<key> <string>errors</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>func_code</string> </key>
<value>
<object>
<klass>
<global name="FuncCode" module="Shared.DC.Scripts.Signature"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>co_argcount</string> </key>
<value> <int>2</int> </value>
</item>
<item>
<key> <string>co_varnames</string> </key>
<value>
<tuple>
<string>id</string>
<string>gid</string>
<string>accounting_list</string>
<string>_getiter_</string>
<string>_getattr_</string>
<string>context</string>
<string>accounting</string>
<string>getattr</string>
</tuple>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>func_defaults</string> </key>
<value>
<tuple>
<string></string>
<string></string>
</tuple>
</value>
</item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Accounting_getAccountingValueList</string> </value> <value> <string>Accounting_getAccountingValueList</string> </value>
</item> </item>
<item>
<key> <string>warnings</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
......
...@@ -74,77 +74,14 @@ return context.REQUEST.RESPONSE.redirect(\n ...@@ -74,77 +74,14 @@ return context.REQUEST.RESPONSE.redirect(\n
url_quote(\'Request sent.\')))\n url_quote(\'Request sent.\')))\n
</string> </value> </string> </value>
</item> </item>
<item>
<key> <string>_code</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>*args, **kw</string> </value> <value> <string>*args, **kw</string> </value>
</item> </item>
<item>
<key> <string>errors</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>func_code</string> </key>
<value>
<object>
<klass>
<global name="FuncCode" module="Shared.DC.Scripts.Signature"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>co_argcount</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>co_varnames</string> </key>
<value>
<tuple>
<string>args</string>
<string>kw</string>
<string>_getitem_</string>
<string>selection_name</string>
<string>Products.PythonScripts.standard</string>
<string>url_quote</string>
<string>_getattr_</string>
<string>context</string>
<string>uids</string>
<string>len</string>
<string>conduit_id</string>
<string>_getiter_</string>
<string>uid</string>
</tuple>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>func_defaults</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>IntegrationModule_doTestCreate</string> </value> <value> <string>IntegrationModule_doTestCreate</string> </value>
</item> </item>
<item>
<key> <string>warnings</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
......
...@@ -53,69 +53,14 @@ ...@@ -53,69 +53,14 @@
<value> <string>return context.getPortalObject()[context.getId()].contentValues()\n <value> <string>return context.getPortalObject()[context.getId()].contentValues()\n
</string> </value> </string> </value>
</item> </item>
<item>
<key> <string>_code</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>*args, **kw</string> </value> <value> <string>*args, **kw</string> </value>
</item> </item>
<item>
<key> <string>errors</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>func_code</string> </key>
<value>
<object>
<klass>
<global name="FuncCode" module="Shared.DC.Scripts.Signature"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>co_argcount</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>co_varnames</string> </key>
<value>
<tuple>
<string>args</string>
<string>kw</string>
<string>_getattr_</string>
<string>_getitem_</string>
<string>context</string>
</tuple>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>func_defaults</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>IntegrationModule_getTestObjectList</string> </value> <value> <string>IntegrationModule_getTestObjectList</string> </value>
</item> </item>
<item>
<key> <string>warnings</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
......
...@@ -98,6 +98,7 @@ ...@@ -98,6 +98,7 @@
<value> <value>
<list> <list>
<string>my_id</string> <string>my_id</string>
<string>my_title</string>
<string>my_int_index</string> <string>my_int_index</string>
<string>my_source_section_title</string> <string>my_source_section_title</string>
<string>my_destination_section_title</string> <string>my_destination_section_title</string>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StringField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_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>
<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>Too much input was given.</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>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</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>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</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>truncate</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>
</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>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</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>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</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>truncate</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>
</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>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </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>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>max_length</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>Title</string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <int>0</int> </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>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -62,69 +62,14 @@ category_list.sort()\n ...@@ -62,69 +62,14 @@ category_list.sort()\n
return category_list\n return category_list\n
</string> </value> </string> </value>
</item> </item>
<item>
<key> <string>_code</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item>
<key> <string>errors</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>func_code</string> </key>
<value>
<object>
<klass>
<global name="FuncCode" module="Shared.DC.Scripts.Signature"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>co_argcount</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>co_varnames</string> </key>
<value>
<tuple>
<string>category_list</string>
<string>_getiter_</string>
<string>_getattr_</string>
<string>context</string>
<string>category</string>
</tuple>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>func_defaults</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>TransactionLine_getCategoryList</string> </value> <value> <string>TransactionLine_getCategoryList</string> </value>
</item> </item>
<item>
<key> <string>warnings</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
......
...@@ -28,10 +28,6 @@ ...@@ -28,10 +28,6 @@
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>portal_integrations</string> </value> <value> <string>portal_integrations</string> </value>
</item> </item>
<item>
<key> <string>last_id</string> </key>
<value> <string>27</string> </value>
</item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
......
...@@ -3,4 +3,4 @@ extension.erp5.TioSafeBrain ...@@ -3,4 +3,4 @@ extension.erp5.TioSafeBrain
extension.erp5.TioSafeDoNothingConduit extension.erp5.TioSafeDoNothingConduit
extension.erp5.TioSafeResourceConduit extension.erp5.TioSafeResourceConduit
extension.erp5.TioSafeTool extension.erp5.TioSafeTool
extension.erp5.TioSafeNodeConduit extension.erp5.TioSafeNodeConduit
\ No newline at end of file
Integration Module | destination_section Integration Module | destination_section
Integration Module | source_section Integration Module | source_section
Integration Site | resource
Integration Site | destination_payment Integration Site | destination_payment
Integration Site | resource
Integration Site | source_account Integration Site | source_account
Integration Site | source_payment Integration Site | source_payment
Synchronization Configurator Item | destination_section Synchronization Configurator Item | destination_section
......
...@@ -53,14 +53,16 @@ ...@@ -53,14 +53,16 @@
<value> <string encoding="cdata"><![CDATA[ <value> <string encoding="cdata"><![CDATA[
# First retrieve the document\n # First retrieve the document\n
doc_list = context.getPortalObject().document_module.searchFolder(reference=reference,\n portal = context.getPortalObject()\n
sort_on = ((\'version\', \'DESC\'),),\n document_list = portal.document_module.searchFolder(\n
limit=1)\n reference=reference,\n
validation_state="shared",\n
sort_on=[(\'version\', \'DESC\')],\n
)\n
if len(document_list) != 1:\n
raise ValueError, "Impossible to find document with reference %s" %(reference)\n
document = document_list[0].getObject()\n
\n \n
if not len(doc_list) == 1:\n
raise ValueError, "Impossible to find document with reference %r" %(reference)\n
\n
import_file = doc_list[0]\n
\n \n
# Then parse it\n # Then parse it\n
from Products.ERP5OOo.OOoUtils import OOoParser\n from Products.ERP5OOo.OOoUtils import OOoParser\n
...@@ -102,7 +104,7 @@ def getIDFromString(string=None):\n ...@@ -102,7 +104,7 @@ def getIDFromString(string=None):\n
\n \n
return clean_id\n return clean_id\n
\n \n
parser.openFromString(str(import_file.getData()))\n parser.openFromString(str(document.getData()))\n
\n \n
# Extract tables from the speadsheet file\n # Extract tables from the speadsheet file\n
filename = parser.getFilename()\n filename = parser.getFilename()\n
...@@ -127,41 +129,42 @@ for table_name in spreadsheet_list.keys():\n ...@@ -127,41 +129,42 @@ for table_name in spreadsheet_list.keys():\n
property_map[column_index] = column_id\n property_map[column_index] = column_id\n
column_index += 1\n column_index += 1\n
# This path_element_list help us to reconstruct the absolute path\n # This path_element_list help us to reconstruct the absolute path\n
context.log("line_id = %r" %(line_id))\n
if line_id is not None:\n if line_id is not None:\n
line_list = [sheet[int(line_id)-1],]\n line_list = [sheet[int(line_id)-1],]\n
line_index = int(line_id)\n line_index = int(line_id)\n
else:\n else:\n
line_list = sheet[1:]\n line_list = sheet[1:]\n
line_index = 2\n line_index = 2\n
context.log(\'line_list = %s\' %(line_list))\n line_list = line_list[:limit]\n
for line in line_list:\n for line in line_list:\n
if id_list and str(line_index) not in id_list:\n
continue\n
# Exclude empty lines\n # Exclude empty lines\n
context.log("\\tgot line", line)\n
if line.count(\'\') + line.count(None) == len(line):\n if line.count(\'\') + line.count(None) == len(line):\n
continue\n continue\n
\n \n
# Prefetch line datas\n # Prefetch line datas\n
line_data = {"id" : str(line_index)}\n line_data = {"id" : str(line_index)}\n
path_defined = []\n if not id_only:\n
for cell_index, cell in enumerate(line):\n path_defined = []\n
# Get the property corresponding to the cell data\n for cell_index, cell in enumerate(line):\n
property_id = property_map[cell_index]\n # Get the property corresponding to the cell data\n
if cell is not None and cell.strip()==\'\':\n property_id = property_map[cell_index]\n
# empty string is NOT a valid identifier\n if cell is not None and cell.strip()==\'\':\n
cell=None\n # empty string is NOT a valid identifier\n
if not cell:\n cell=None\n
continue\n if not cell:\n
if line_data.has_key(property_id):\n continue\n
if isinstance(line_data[property_id], str):\n if line_data.has_key(property_id):\n
cell_value_list = [line_data[property_id], cell]\n if isinstance(line_data[property_id], str):\n
line_data[property_id] = cell_value_list\n cell_value_list = [line_data[property_id], cell]\n
line_data[property_id] = cell_value_list\n
else:\n
line_data[property_id].append(cell)\n
else:\n else:\n
line_data[property_id].append(cell)\n line_data[property_id] = cell\n
else:\n # Proceed to next cell\n
line_data[property_id] = cell\n cell_index += 1\n
# Proceed to next cell\n
cell_index += 1\n
line_index += 1\n line_index += 1\n
spreadsheet_line_list.append(line_data)\n spreadsheet_line_list.append(line_data)\n
\n \n
...@@ -172,7 +175,7 @@ return spreadsheet_line_list\n ...@@ -172,7 +175,7 @@ return spreadsheet_line_list\n
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>reference, table, line_id=None</string> </value> <value> <string>reference, table, limit, id_only, line_id=None, id_list=None</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
1 4
\ No newline at end of file \ No newline at end of file
...@@ -5155,7 +5155,7 @@ Business Template is a set of definitions, such as skins, portal types and categ ...@@ -5155,7 +5155,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
from Products.ERP5VCS.WorkingCopy import NotAWorkingCopyError from Products.ERP5VCS.WorkingCopy import NotAWorkingCopyError
try: try:
self.setRevision(self.getVcsTool().newRevision()) self.setRevision(self.getVcsTool().newRevision())
except NotAWorkingCopyError: except (NotAWorkingCopyError, IOError):
raise ImportError raise ImportError
except ImportError: except ImportError:
self.updateRevisionNumber() self.updateRevisionNumber()
......
...@@ -31,11 +31,12 @@ from hashlib import md5 ...@@ -31,11 +31,12 @@ from hashlib import md5
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type.XMLObject import XMLObject from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet from Products.ERP5Type import PropertySheet
from Products.ERP5SyncML.Utils import PdataHelper from Products.ERP5SyncML.Utils import PdataHelper
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
_MARKER = [] _MARKER = []
...@@ -50,7 +51,6 @@ class SyncMLSignature(XMLObject): ...@@ -50,7 +51,6 @@ class SyncMLSignature(XMLObject):
""" """
meta_type = 'ERP5 Signature' meta_type = 'ERP5 Signature'
portal_type = 'SyncML Signature' portal_type = 'SyncML Signature'
isIndexable = ConstantGetter('isIndexable', value=False) isIndexable = ConstantGetter('isIndexable', value=False)
security = ClassSecurityInfo() security = ClassSecurityInfo()
...@@ -66,6 +66,32 @@ class SyncMLSignature(XMLObject): ...@@ -66,6 +66,32 @@ class SyncMLSignature(XMLObject):
, PropertySheet.Document , PropertySheet.Document
, PropertySheet.SyncMLSignature ) , PropertySheet.SyncMLSignature )
security.declareProtected(Permissions.ModifyPortalContent, 'synchronize')
def synchronize(self):
"""
This is call when subscription get confirmation of the data synchronization
This copy & reset some properties if needed
"""
edit_kw = {}
temporary_data = self.getTemporaryData()
if temporary_data is not None:
# This happens when we have sent the xml
# and we just get the confirmation
self.setData(temporary_data)
edit_kw["temporary_data"] = None
if self.isForce():
edit_kw["force"] = False
if self.hasPartialData():
edit_kw["partial_data"] = None
if self.hasSubscriberXupdate():
edit_kw["subscriber_xupdate"] = None
if self.hasPublisherXupdate():
edit_kw["publisher_xupdate"] = None
if len(edit_kw):
self.edit(**edit_kw)
security.declareProtected(Permissions.ModifyPortalContent, 'setData') security.declareProtected(Permissions.ModifyPortalContent, 'setData')
def setData(self, value): def setData(self, value):
""" """
...@@ -249,24 +275,6 @@ class SyncMLSignature(XMLObject): ...@@ -249,24 +275,6 @@ class SyncMLSignature(XMLObject):
else: else:
return self._baseGetPublisherXupdate(default) return self._baseGetPublisherXupdate(default)
security.declareProtected(Permissions.ModifyPortalContent,
'reset')
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()
security.declareProtected(Permissions.ModifyPortalContent, security.declareProtected(Permissions.ModifyPortalContent,
'getConflictList') 'getConflictList')
def getConflictList(self): def getConflictList(self):
...@@ -275,22 +283,6 @@ class SyncMLSignature(XMLObject): ...@@ -275,22 +283,6 @@ class SyncMLSignature(XMLObject):
""" """
return self.contentValues() return self.contentValues()
security.declareProtected(Permissions.ModifyPortalContent,
'setConflictList')
def setConflictList(self, conflict_list):
"""
XXX is it still usefull ?
"""
return
security.declareProtected(Permissions.ModifyPortalContent,
'resetConflictList')
def resetConflictList(self):
"""
XXX is it still usefull ?
"""
return
security.declareProtected(Permissions.ModifyPortalContent, security.declareProtected(Permissions.ModifyPortalContent,
'delConflict') 'delConflict')
def delConflict(self, conflict): def delConflict(self, conflict):
......
...@@ -28,7 +28,6 @@ ...@@ -28,7 +28,6 @@
############################################################################## ##############################################################################
from base64 import b16encode, b16decode from base64 import b16encode, b16decode
from warnings import warn
from logging import getLogger from logging import getLogger
from urlparse import urlparse from urlparse import urlparse
from lxml import etree from lxml import etree
...@@ -66,6 +65,8 @@ syncml_logger = getLogger('ERP5SyncML') ...@@ -66,6 +65,8 @@ syncml_logger = getLogger('ERP5SyncML')
MAX_OBJECT_PER_MESSAGE = 300 MAX_OBJECT_PER_MESSAGE = 300
RETRO_COMPATIBLE = True
_MARKER = [] _MARKER = []
class SyncMLSubscription(XMLObject): class SyncMLSubscription(XMLObject):
""" """
...@@ -99,14 +100,164 @@ class SyncMLSubscription(XMLObject): ...@@ -99,14 +100,164 @@ class SyncMLSubscription(XMLObject):
self.logout() self.logout()
self._edit(authenticated_user=None) self._edit(authenticated_user=None)
security.declarePrivate('getAndIndex')
def getAndIndex(self, callback, method_kw, activate_kw, **kw):
"""
This methods is called by the asynchronous engine to index source
data in sql table
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
"""
if kw.has_key("packet_size"):
search_kw = dict(kw)
packet_size = search_kw.pop('packet_size', 30)
limit = packet_size * search_kw.pop('activity_count', 100)
else:
# We index everything at once
limit=None
packet_size=None
search_kw={}
try:
r = self.getDocumentIdList(limit=limit, **search_kw) # It is assumed that
# the result is sorted
except TypeError:
if not RETRO_COMPATIBLE:
raise
else:
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)
if result_count:
r = [str(x.path) for x in r]
if not limit:
# We do not split in activity so call the callback right now
syncml_logger.info("getAndIndex : got %d result and no limit, calling callback..." %
(result_count,))
callback_method = getattr(self, callback)
callback_method(path_list=r[:],
activate_kw=activate_kw,
**method_kw)
else:
syncml_logger.info("getAndIndex : got %d, %r result, limit = %r, packet %r" %
(result_count, r, limit, packet_size))
generated_other_activity = False
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.info("--> calling getAndIndex in activity, min = %s" %
(kw["min_id"],))
self.activate(**next_kw).getAndIndex(
callback, method_kw, activate_kw, **kw)
generated_other_activity = True
activate = self.activate
callback_method = getattr(activate(**activate_kw), callback)
if generated_other_activity:
for i in xrange(0, result_count, packet_size):
syncml_logger.info("-- getAndIndex : recursive call, generating for %s"
% (r[i:i+packet_size],))
callback_method(path_list=r[i:i+packet_size],
activate_kw=activate_kw,
**method_kw)
else:
if result_count > packet_size and limit:
for i in xrange(0, result_count-packet_size, packet_size):
syncml_logger.info("-- getAndIndex : i %s, call, generating for %s : %s" %
(i, r[i:i+packet_size], activate_kw))
callback_method(path_list=r[i:i+packet_size],
**method_kw)
final_min = i + packet_size
else:
final_min = 0
syncml_logger.info("---- getAndIndex : final call for %s %s : %s" \
%(final_min, r[final_min:], activate_kw))
callback_method(path_list=r[final_min:],
activate_kw=activate_kw,
**method_kw)
return result_count
security.declarePrivate('generateBaseResponse')
def generateBaseResponse(self, message_id=None):
"""
Return a message containing default headers
"""
if not message_id:
message_id=self.getNextMessageId(),
syncml_response = SyncMLResponse()
syncml_response.addHeader(
session_id=self.getSessionId(),
message_id=message_id,
target=self.getUrlString(),
source=self.getSubscriptionUrlString())
syncml_response.addBody()
return syncml_response
security.declarePrivate('getSearchableSourcePath')
def getSearchableSourcePath(self):
"""
Return the path of the subscription that will be used in sql table
_ char must be escaped because of the LIKE behaviour
"""
return "%s%%" % (self.getSourceValue().getPath().replace("_","\_"),)
def sendSyncCommand(self, min_gid, max_gid, message_id, activate_kw):
"""
This methods is intented to be called by asynchronous engine in activity to
send sync commands for a subset of data
"""
# Build Message
syncml_response = SyncMLResponse()
syncml_response = self.generateBaseResponse(message_id)
self._getSyncMLData(
syncml_response=syncml_response,
min_gid=min_gid,
max_gid=max_gid,
)
# Send the message in activity to prevent recomputation of data in case of
# transport failure
# activate_kw["group_method_id"] = None
# activate_kw["group_method_cost"] = .05
self.activate(**activate_kw).sendMessage(xml=str(syncml_response))
security.declarePrivate('applySyncCommand')
def applySyncCommand(self, response_message_id, activate_kw, **kw):
"""
This methods is intented to be called by asynchronous engine in activity to
apply sync commands for a subset of data
"""
# Build Message
if response_message_id:
syncml_response = self.generateBaseResponse()
else:
syncml_response = None
self._applySyncCommand(syncml_response=syncml_response, **kw)
# Send the message in activity to prevent recomputing data in case of
# transport failure
if syncml_response:
syncml_logger("---- %s sending %s notifications of sync"
% (self.getTitle(),
syncml_response.sync_confirmation_counter))
self.activate(activity="SQLQueue",
# group_method_id=None,
# group_method_cost=.05,
tag=activate_kw).sendMessage(xml=str(syncml_response))
security.declarePrivate('getAndActivate') security.declarePrivate('getAndActivate')
def getAndActivate(self, callback, method_kw, activate_kw, **kw): def getAndActivate(self, callback, activate_kw, **kw):
""" """
This methods is called by the asynchronous engine to split activity This methods is called by the asynchronous engine to split activity
generation into activities. generation into activities.
callback : method to call in activity callback : method to call in activity
method_kw : callback's parameters
activate_kw : activity parameters to pass to activate call activate_kw : activity parameters to pass to activate call
kw : any parameter getAndActivate can required if it calls itself kw : any parameter getAndActivate can required if it calls itself
...@@ -121,59 +272,89 @@ class SyncMLSubscription(XMLObject): ...@@ -121,59 +272,89 @@ class SyncMLSubscription(XMLObject):
search_kw = dict(kw) search_kw = dict(kw)
packet_size = search_kw.pop('packet_size', 30) packet_size = search_kw.pop('packet_size', 30)
limit = packet_size * search_kw.pop('activity_count', 100) limit = packet_size * search_kw.pop('activity_count', 100)
try: syncml_logger.debug("--> calling getAndActivate packet size = %s, limit = %s" %
r = self.getDocumentIdList(limit=limit, **search_kw) # It is assumed that (packet_size, limit))
# the result is sorted # We must know if we have a lower limit or not to propagate
except TypeError: if not kw.has_key("strict_min_gid"):
syncml_logger.warning("Script %s does not accept paramaters limit=%s kw=%s" % first_call = True
(self.getListMethodId(), limit, search_kw,)) else:
r = self.getDocumentList() # It is assumed that first_call = False
# the result is sorted
search_kw.update({"stict_min_gid" : None,
"min_gid" : None,
"max_gid" : None,
"limit" : limit,
"path" : self.getSearchableSourcePath()})
r = [x.gid for x in self.z_get_syncml_gid_list(**search_kw)]
result_count = len(r) result_count = len(r)
generated_other_activity = False generated_other_activity = False
if result_count: if result_count:
syncml_logger.debug("getAndActivate : got %d result, limit = %d, packet %d" % syncml_logger.info("getAndActivate : got %d result" % (result_count,))
(result_count, limit, packet_size))
if result_count == limit: if result_count == limit:
# Recursive call to prevent too many activity generation # Recursive call to prevent too many activity generation
next_kw = dict(activate_kw, priority=1+activate_kw.get('priority', 1)) next_kw = dict(activate_kw, priority=1+activate_kw.get('priority', 1))
kw["min_id"] = r[-1].getId() kw["strict_min_gid"] = r[-1]
syncml_logger.debug("--> calling getAndActivate in activity, min = %s" % syncml_logger.info("--> calling getAndActivate in activity, min = %s" %
(kw["min_id"],)) (kw.get("strict_min_gid", None),))
self.activate(**next_kw).getAndActivate( self.activate(**next_kw).getAndActivate(
callback, method_kw, activate_kw, **kw) callback, activate_kw, **kw)
generated_other_activity = True generated_other_activity = True
r = [x.getId() for x in r]
message_id_list = self.getNextMessageIdList(id_count=result_count) message_id_list = self.getNextMessageIdList(id_count=result_count)
# XXX maybe (result_count / packet_size) + 1 instead of 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 message_id_list.reverse() # We pop each id in the following loop
activate = self.getPortalObject().portal_synchronizations.activate callback_method = getattr(self.activate(**activate_kw), callback)
callback_method = getattr(activate(**activate_kw), callback)
if generated_other_activity: if generated_other_activity:
# XXX Can be factorized with following code
# upper_limit of xrange + some check ???
for i in xrange(0, result_count, packet_size): for i in xrange(0, result_count, packet_size):
syncml_logger.debug("-- getAndActivate : recursive call, generating for %s" if first_call:
% (r[i:i+packet_size],)) min_gid = None
callback_method(id_list=r[i:i+packet_size], first_call = False
else:
min_gid = r[i]
try:
max_gid = r[i+packet_size-1]
except IndexError:
# Last packet
max_gid = r[-1]
syncml_logger.info("-- getAndActivate : recursive call i = %s, min = %s, max = %s" \
% (i, min_gid, max_gid,))
callback_method(min_gid=min_gid,
max_gid=max_gid,
message_id=message_id_list.pop(), message_id=message_id_list.pop(),
activate_kw=activate_kw, activate_kw=activate_kw)
**method_kw)
else: else:
i = 0 i = 0
for i in xrange(0, result_count-packet_size, packet_size): if result_count > packet_size:
syncml_logger.debug("-- getAndActivate : call, generating for %s : %s" % for i in xrange(0, result_count-packet_size, packet_size):
(r[i:i+packet_size], activate_kw)) if first_call:
callback_method(id_list=r[i:i+packet_size], min_gid = None
message_id=message_id_list.pop(), first_call = False
activate_kw=activate_kw, else:
**method_kw) min_gid = r[i]
# Final activity must be executed after all other syncml_logger.info("-- getAndActivate : call min = %s, max = %s" \
syncml_logger.debug("---- getAndActivate : final call for %s : %s" %(r[i+packet_size:], activate_kw)) % (min_gid, r[i+packet_size-1]))
callback_method(id_list=r[i+packet_size:], # XXX Has to be unit tested callback_method(min_gid=min_gid,
# with mock object max_gid=r[i+packet_size-1],
message_id=message_id_list.pop(),
activate_kw=activate_kw)
final_min = i + packet_size
else:
final_min = 0
# Final activity must be tell there is no upper limit
# XXX maybe re-put here the final tag of message to avoid empty message
if first_call:
min_gid = None
else:
min_gid = r[final_min]
syncml_logger.info("-- getAndActivate : final call min = %s, max = None" \
% (min_gid,))
callback_method(min_gid=min_gid,
max_gid=None, # No limit when last call
message_id=message_id_list.pop(), message_id=message_id_list.pop(),
activate_kw=activate_kw, activate_kw=activate_kw)
**method_kw)
return result_count return result_count
security.declarePrivate('sendMessage') security.declarePrivate('sendMessage')
...@@ -207,7 +388,6 @@ class SyncMLSubscription(XMLObject): ...@@ -207,7 +388,6 @@ class SyncMLSubscription(XMLObject):
sync_id=self.getDestinationReference(), sync_id=self.getDestinationReference(),
content_type=self.getContentType()) content_type=self.getContentType())
def _loginUser(self, user_id=None): def _loginUser(self, user_id=None):
""" """
Log in with the user provided or defined on self Log in with the user provided or defined on self
...@@ -217,7 +397,11 @@ class SyncMLSubscription(XMLObject): ...@@ -217,7 +397,11 @@ class SyncMLSubscription(XMLObject):
if user_id: if user_id:
# TODO: make it work for users existing anywhere # TODO: make it work for users existing anywhere
user_folder = self.getPortalObject().acl_users user_folder = self.getPortalObject().acl_users
user = user_folder.getUserById(user_id).__of__(user_folder) # __of__ might got AttributeError try:
user = user_folder.getUserById(user_id).__of__(user_folder) # __of__ might got AttributeError
except AttributeError:
raise ValueError("User %s cannot be found in user folder, \
synchronization cannot work with this kind of user" % (user_id,))
if user is None: if user is None:
raise ValueError("User %s cannot be found in user folder, \ raise ValueError("User %s cannot be found in user folder, \
synchronization cannot work with this kind of user" % (user_id,)) synchronization cannot work with this kind of user" % (user_id,))
...@@ -229,16 +413,20 @@ class SyncMLSubscription(XMLObject): ...@@ -229,16 +413,20 @@ class SyncMLSubscription(XMLObject):
% (self.getRelativeUrl())) % (self.getRelativeUrl()))
# XXX To be done later security.declarePrivate('applyActionList')
def _applyAddCommand(self,): def applyActionList(self, syncml_request, syncml_response, simulate=False):
""" """
Apply the add command received, when document already exits, we Browse the list of sync command received, apply them and generate answer
do a kind of "Replace" command instead
""" """
pass 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)
security.declarePrivate('applySyncCommand') security.declarePrivate('applySyncCommand')
def applySyncCommand(self, action, request_message_id, syncml_response, def _applySyncCommand(self, action, request_message_id, syncml_response,
simulate=False): simulate=False):
""" """
Apply a sync command received Apply a sync command received
...@@ -255,6 +443,7 @@ class SyncMLSubscription(XMLObject): ...@@ -255,6 +443,7 @@ class SyncMLSubscription(XMLObject):
# First retrieve the GID of the object we want to modify # First retrieve the GID of the object we want to modify
gid = action["source"] or action["target"] gid = action["source"] or action["target"]
# Retrieve the signature for the current GID # Retrieve the signature for the current GID
path_list = []
signature = self.getSignatureFromGid(gid) signature = self.getSignatureFromGid(gid)
if syncml_response is not None: # No response to send when no signature to create if syncml_response is not None: # No response to send when no signature to create
document = self.getDocumentFromGid(gid) document = self.getDocumentFromGid(gid)
...@@ -265,15 +454,12 @@ class SyncMLSubscription(XMLObject): ...@@ -265,15 +454,12 @@ class SyncMLSubscription(XMLObject):
portal_type='SyncML Signature', portal_type='SyncML Signature',
id=gid, id=gid,
) )
syncml_logger.debug("Created a signature for %s - document : %s" syncml_logger.info("Created a signature for %s - document : %s"
% (signature.getPath(), document)) % (signature.getPath(), document))
if document is not None: if document is not None:
signature.setReference(document.getPath()) signature.setReference(document.getPath())
elif signature.getValidationState() == 'synchronized': path_list.append(signature.getPath())
# Reset status of signature synchronization
signature.drift()
force = signature.isForce() # XXX-must check the use of this later force = signature.isForce() # XXX-must check the use of this later
else: else:
force = True # Always erease data in this mode force = True # Always erease data in this mode
...@@ -291,7 +477,6 @@ class SyncMLSubscription(XMLObject): ...@@ -291,7 +477,6 @@ class SyncMLSubscription(XMLObject):
if not action['more_data']: if not action['more_data']:
# This is the last chunk of a partial xml # This is the last chunk of a partial xml
# or this is just an entire data chunk # or this is just an entire data chunk
if signature and signature.hasPartialData(): if signature and signature.hasPartialData():
# Build data with already stored data # Build data with already stored data
signature.appendPartialData(incoming_data) signature.appendPartialData(incoming_data)
...@@ -304,7 +489,7 @@ class SyncMLSubscription(XMLObject): ...@@ -304,7 +489,7 @@ class SyncMLSubscription(XMLObject):
if document is None: if document is None:
# This is the default behaviour when getting an "Add" command # This is the default behaviour when getting an "Add" command
# we create new document from the received data # we create new document from the received data
syncml_logger.debug("Calling addNode with no previous document found") syncml_logger.info("Calling addNode with no previous document found")
add_data = conduit.addNode(xml=incoming_data, add_data = conduit.addNode(xml=incoming_data,
object=destination, object=destination,
signature=signature, signature=signature,
...@@ -390,23 +575,29 @@ class SyncMLSubscription(XMLObject): ...@@ -390,23 +575,29 @@ class SyncMLSubscription(XMLObject):
elif action['command'] == 'Delete': elif action['command'] == 'Delete':
status_code="success" status_code="success"
document = self.getDocumentFromGid(signature.getId()) document = self.getDocumentFromGid(signature.getId())
syncml_logger.info("Deleting signature %s & doc %s" %(signature.getPath(),
document.getPath()))
path_list.remove(signature.getPath())
if document is not None: if document is not None:
# XXX Can't we get conflict ? # XXX Can't we get conflict ?
# XXX Review the code to prevent retrieving document
conduit.deleteNode(xml=incoming_data, conduit.deleteNode(xml=incoming_data,
object=destination, object=destination,
object_id=document.getId()) object_id=document.getId())
# Delete signature # Delete signature
self._delObject(gid) self._delObject(gid)
else: else:
syncml_logger.error("Document with gid is already deleted" syncml_logger.error("Document with gid %s is already deleted"
% (gid,)) % (gid,))
self.z_delete_data_from_path(path="%s" %(signature.getPath(),))
else: else:
raise ValueError("Unknown command %s" %(action['command'],)) raise ValueError("Unknown command %s" %(action['command'],))
# Now update signature status regarding conflict list # Now update signature status regarding conflict list
if action['command'] != "Delete" and signature: if action['command'] != "Delete" and signature:
if len(conflict_list): if len(conflict_list):
status_code="conflict" status_code = "conflict"
signature.changeToConflict() signature.changeToConflict()
# Register the data received which generated the diff # Register the data received which generated the diff
# XXX Why ? # XXX Why ?
...@@ -417,8 +608,8 @@ class SyncMLSubscription(XMLObject): ...@@ -417,8 +608,8 @@ class SyncMLSubscription(XMLObject):
else: else:
signature.setData(str(xml_document)) signature.setData(str(xml_document))
signature.synchronize() signature.synchronize()
syncml_logger.debug("change state of signature to %s" syncml_logger.info("change state of signature to %s with %s"
% (signature.getValidationState(),)) % (signature.getValidationState(), signature.getData()))
if signature: if signature:
# Generate status about the object synchronized # Generate status about the object synchronized
...@@ -432,9 +623,8 @@ class SyncMLSubscription(XMLObject): ...@@ -432,9 +623,8 @@ class SyncMLSubscription(XMLObject):
message_ref=request_message_id) message_ref=request_message_id)
else: # We want to retrieve more data else: # We want to retrieve more data
syncml_logger.debug("we need to retrieve more data for %s" % (signature,)) syncml_logger.info("we need to retrieve more data for %s"
if signature.getValidationState() != 'partial': % (signature.getRelativeUrl(),))
signature.changeToPartial()
signature.appendPartialData(incoming_data) signature.appendPartialData(incoming_data)
# XXX Must check if size is present into the xml # XXX Must check if size is present into the xml
# if not, client might ask it to server with a 411 alert # if not, client might ask it to server with a 411 alert
...@@ -455,345 +645,271 @@ class SyncMLSubscription(XMLObject): ...@@ -455,345 +645,271 @@ class SyncMLSubscription(XMLObject):
source=self.getSourceReference(), source=self.getSourceReference(),
last_anchor=self.getLastAnchor(), last_anchor=self.getLastAnchor(),
next_anchor=self.getNextAnchor()) next_anchor=self.getNextAnchor())
# Index signature with their new value
if len(path_list):
self.SQLCatalog_indexSyncMLDocumentList(path_list)
def _sendFinalMessage(self):
security.declarePrivate('applyActionList')
def applyActionList(self, syncml_request, syncml_response, simulate=False):
""" """
Browse the list of sync command received, apply them and generate answer Send an empty message containing the final tag to notify the end of
the "sending_modification" stage of the synchronization
""" """
for action in syncml_request.sync_command_list: syncml_response = self.generateBaseResponse()
self.applySyncCommand( syncml_response.addFinal()
action=action,
request_message_id=syncml_request.header["message_id"],
syncml_response=syncml_response,
simulate=simulate)
def _getDeletedData(self, syncml_response=None): final_activate_kw = {
""" 'after_method_id' : ("processServerSynchronization",
Add delete command to syncml resposne "processClientSynchronization"),
""" 'priority' :ACTIVITY_PRIORITY + 1,
# XXX property must be renamed to activity_enabled 'tag' : "%s_delete" %(self.getRelativeUrl(),)
if self.getIsActivityEnabled(): }
self.recurseCallMethod( syncml_logger.info("Sending final message for modificationson on %s"
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(),)) % (self.getRelativeUrl(),))
id_list = [x.getId() for x in self.objectValues() if \ self.activate(**final_activate_kw).sendMessage(xml=str(syncml_response))
x.getValidationState() == "not_synchronized"]
for gid in id_list:
syncml_response.addDeleteCommand(gid=gid)
def _sendFinalMessage(self): def getDeletedSyncMLData(self, syncml_response=None):
""" """
Send an empty message containing the final tag to notify the end of Retrieve & generate the syncml message for messages that were deleted
the "sending_modification" stage of the synchronization This message also contains the final tag to let know that the sending
of modification is over
""" """
syncml_response = SyncMLResponse() if not syncml_response:
syncml_response.addHeader( syncml_response = self.generateBaseResponse()
session_id=self.getSessionId(),
message_id=self.getNextMessageId(), # Compare gid between signature & source to know which data were deleted
target=self.getUrlString(), deleted_signature_set = self.z_get_syncml_deleted_gid_list(
source=self.getSubscriptionUrlString()) signature_path=self.getSearchablePath(),
syncml_response.addBody() source_path=self.getSearchableSourcePath())
syncml_logger.info("\t---> delete signature are %r" % (len(deleted_signature_set)))
for r in deleted_signature_set:
syncml_response.addDeleteCommand(gid=r.gid)
syncml_logger.info("\t\t---> %r" % (r.gid))
syncml_response.addFinal() syncml_response.addFinal()
# Now send the message
final_activate_kw = { final_activate_kw = {
'after_method_id' : ("processServerSynchronization", 'after_method_id' : ("processServerSynchronization",
"processClientSynchronization"), "processClientSynchronization"),
'priority' :ACTIVITY_PRIORITY + 1, 'priority' :ACTIVITY_PRIORITY + 1,
'tag' : "%s_delete" %(self.getRelativeUrl(),) 'tag' : "%s_delete" %(self.getRelativeUrl(),)
} }
syncml_logger.warning("Sending final message for modificationson on %s" syncml_logger.info("Sending final message for modificationson on %s"
% (self.getRelativeUrl(),)) % (self.getRelativeUrl(),))
self.activate(**final_activate_kw).sendMessage(xml=str(syncml_response)) self.activate(**final_activate_kw).sendMessage(xml=str(syncml_response))
def getSearchablePath(self):
def checkAndSendDeleteMessage(self, message_list): return "%s%%" %(self.getPath().replace('_', '\_'),)
"""
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): def _generateSyncCommand(self, action, signature, data_diff ,document_data, gid,
conduit, syncml_response):
""" """
XXX Comment to be fixed Generate a sync command for a given data
""" """
if not id_list: more_data = False
syncml_logger.warning("Non optimal call to _getSyncMLData, no id list provided : %r" %(id_list)) if signature:
else: if len(data_diff) > MAX_LEN and not self.getIsActivityEnabled():
syncml_logger.info("getSyncMLData, id list provided %s" % (id_list,)) # XXX-Aurel : I do not think splitting is working when running in activity
syncml_logger.info("data for %s too big, splitting..." %(signature.getPath(),))
more_data = True
data_diff, rest_string = cutXML(data_diff, MAX_LEN)
# Store the remaining data to send it later
signature.setPartialData(rest_string)
signature.setPartialAction(action)
else:
# The data will be copied in 'data' property once we get
# confirmation that the document was well synchronized
signature.setTemporaryData(document_data)
# Generate the message
syncml_logger.info("adding sync command %s for %s" %(action, gid))
syncml_response.addSyncCommand(
sync_command=action,
gid=gid,
data=data_diff,
more_data=more_data,
media_type=conduit.getContentType())
return more_data
def _getSyncMLData(self, syncml_response, min_gid, max_gid):
"""
Compare data from source with data stored in signature from previous
synchronization. If there is any change, add command into the syncml
message
syncml_response : SyncML message to fill with command
min_gid = the lower limit for browsing data
max_gid = the upper limit for browsing data
"""
syncml_logger.info("getSyncMLData, min %s - max %r" % (min_gid, max_gid,))
conduit = self.getConduit() conduit = self.getConduit()
finished = True portal = self.getPortalObject()
traverse = portal.restrictedTraverse
if isinstance(conduit, basestring): # Check deletion now ?
conduit = getConduitByName(conduit) if portal.portal_preferences.getPreferredCheckDeleteAtEnd() is False:
raise NotImplementedError
try: object_list = self.z_get_syncml_path_list(
object_list = self.getDocumentList(id_list=id_list) min_gid=min_gid,
except TypeError: max_gid=max_gid,
# Old style script path=self.getSearchableSourcePath())
warn("Script %s does not accept id_list paramater" %
(self.getListMethodId(),), DeprecationWarning) syncml_logger.info("getSyncMLData, object list is %s" % ([x.path for x in object_list]))
object_list = self.getDocumentList()
loop = 0
traverse = self.getPortalObject().restrictedTraverse
alert_code = self.getSyncmlAlertCode() alert_code = self.getSyncmlAlertCode()
sync_all = alert_code in ("refresh_from_client_only", "slow_sync") sync_all = alert_code in ("refresh_from_client_only", "slow_sync")
# XXX Quick & dirty hack to prevent signature creation, this must be defined # XXX Quick & dirty hack to prevent signature creation, this must be defined
# on pub/sub instead # on pub/sub instead
create_signature = alert_code != "refresh_from_client_only" create_signature = alert_code != "refresh_from_client_only"
if not len(object_list) and id_list: if not len(object_list) and (min_gid or max_gid):
syncml_logger.warning("No object retrieved althoud id_list (%s) is provided" raise ValueError("No object retrieved althoud min/max gid (%s/%s) is provided"
% (id_list)) % (min_gid, max_gid))
more_data = False
for result in object_list: for result in object_list:
object_path = result.getPath() # XXX We need a way to stop the loop when we reach a given packet size
# if loop >= max_range: document_path = result.path
# # For now, maximum object list is always none, so we will never come here ! gid = result.gid
# syncml_logger.warning("...Send too many objects, will split message...") document_data = result.data
# finished = False signature = self.getSignatureFromGid(gid)
# break if signature:
# Get the GID syncml_logger.info("signature is %s = %s" %(signature.getRelativeUrl(),
document = traverse(object_path) signature.getValidationState()))
gid = self.getGidFromObject(document)
if not gid: if not document_data:
raise ValueError("Impossible to compute gid for %s" %(object_path)) raise ValueError("No data for %s / %s" %(gid, document_path))
if True: # not loop: # or len(syncml_response) < MAX_LEN: # For the case it was never synchronized, we have to send everything
# XXX must find a better way to prevent sending if not signature or sync_all:
# no object due to a too small limit # Either it is the first time we get this object
signature = self.getSignatureFromGid(gid) # either the synchronization process required
more_data = False # to send every data again as if it was never done before
# For the case it was never synchronized, we have to send everything if create_signature:
if not signature or sync_all: if not signature:
# First time we send this object or the synchronization more required signature = self.newContent(portal_type='SyncML Signature',
# to send every data as it was never synchronized before id=gid,
document_data = conduit.getXMLFromObjectWithId( reference=document_path,
# XXX To be renamed (getDocumentData) independant from format temporary_data=document_data)
document, syncml_logger.info("Created a signature %s for gid = %s, path %s"
xml_mapping=self.getXmlBindingGeneratorMethodId(), % (signature.getPath(), gid, document_path))
context_document=self.getPath()) more_data = self._generateSyncCommand(
action=ADD_ACTION,
if not document_data: signature=signature,
continue data_diff=document_data,
document_data=document_data,
gid=gid,
conduit=conduit,
syncml_response=syncml_response)
elif signature.hasPartialData():
# Case of partially sent data
# XXX Cutting must be managed by conduit
# Here it is too specific to XML data
xml_string = signature.getFirstPdataChunk(MAX_LEN)
if signature.hasPartialData():
more_data = True
# We need to convert XML to a CDATA type to prevent collision
# with syncml's XML
document_data = etree.CDATA(xml_string.decode('utf-8'))
syncml_logger.info("adding partial sync command for %s" %(gid,))
syncml_response.addSyncCommand(
sync_command=signature.getPartialAction(),
gid=gid,
data=document_data,
more_data=more_data,
media_type=conduit.getContentType())
if create_signature: if not more_data:
if not signature: syncml_logger.info("signature %s is syncing from partial"
signature = self.newContent(portal_type='SyncML Signature', % (signature.getRelativeUrl(),))
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: elif signature.getValidationState() in ('no_conflict',
# Store the new representation of the document 'conflict_resolved_with_merge'):
# It will be copy to "data" property once synchronization # We don't have synchronized this object yet but it has a signature
# is confirmed if signature.getValidationState() == 'conflict_resolved_with_merge':
signature.setTemporaryData(xml_object) # XXX Why putting confirmation message here
signature.doSync() # Server can get confirmation of sync although it has not yet
syncml_logger.debug("signature %s is syncing" # send its data modification to the client
% (signature.getRelativeUrl(),)) # This must be checked against specifications
# Right now, this message will tell the other side to apply the
# diff without checking conflicts
# Generate the command # We then send the modifications
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( syncml_response.addConfirmationMessage(
target_ref=gid, source_ref=gid,
sync_code='conflict_resolved_with_client_command_winning', sync_code='conflict_resolved_with_merge',
command='Replace') 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: syncml_logger.info("\tMD5 is %s for %s" %((signature.checkMD5(document_data)),
signature.doSync() signature.getReference()))
syncml_logger.debug("signature %s is syncing" if not signature.checkMD5(document_data):
# MD5 checksum tell there is a modification of the object
# XXX this diff generation must managed by the conduit
# we just need to have conduit.generateDocumentDiff(new_data, former_data)
if conduit.getContentType() != 'text/xml':
# If there is no xml, we re-send the whole object
data_diff = document_data
else:
# Compute the diff
new_document = conduit.replaceIdFromXML(document_data, '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
syncml_logger.info("\tFake diff, signature %s is synchronized"
% (signature.getRelativeUrl(),)) % (signature.getRelativeUrl(),))
elif signature.getValidationState() in ('syncing', 'synchronized'): continue
raise ValueError("Must not get signature in %s state here, signature is %s"
% (signature.getValidationState(),
signature.getPath(),))
if not more_data: # Reindex modified document
pass syncml_logger.info("\tGot a diff for %s : %s" %(gid, data_diff))
else: more_data = self._generateSyncCommand(
syncml_logger.info("Splitting document") action=REPLACE_ACTION,
break signature=signature,
else: data_diff=data_diff,
syncml_logger.warning("Package is going to be splitted") document_data=document_data,
gid=gid,
conduit=conduit,
syncml_response=syncml_response)
elif signature.getValidationState() == \
'conflict_resolved_with_client_command_winning':
# We have decided to apply the update
# XXX previous_xml will be getXML 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=traverse(document_path),
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(),))
if more_data:
syncml_logger.info("Splitting document")
break break
loop += 1
syncml_logger.debug("_getSyncMLData end with finished %s" syncml_logger.info("_getSyncMLData end with more_data %s"
% (finished,)) % (more_data,))
return finished return not more_data
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getConduit') 'getConduit')
...@@ -828,6 +944,18 @@ class SyncMLSubscription(XMLObject): ...@@ -828,6 +944,18 @@ class SyncMLSubscription(XMLObject):
else: else:
return self._baseGetXmlBindingGeneratorMethodId(default=default) return self._baseGetXmlBindingGeneratorMethodId(default=default)
security.declareProtected(Permissions.AccessContentsInformation,
'getDataFromDocument')
def getDataFromDocument(self, document):
"""
Return the data (xml or other) for a given document
"""
return self.getConduit().getXMLFromObjectWithId(
document,
xml_mapping=self.getXmlBindingGeneratorMethodId(),
context_document=self.getPath())
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getGidFromObject') 'getGidFromObject')
def getGidFromObject(self, object, encoded=True): def getGidFromObject(self, object, encoded=True):
...@@ -900,7 +1028,10 @@ class SyncMLSubscription(XMLObject): ...@@ -900,7 +1028,10 @@ class SyncMLSubscription(XMLObject):
try: try:
result_list = query_method(context_document=self, **kw) result_list = query_method(context_document=self, **kw)
except TypeError: except TypeError:
result_list = query_method(**kw) if not RETRO_COMPATIBLE:
raise
else:
result_list = query_method(**kw)
else: else:
raise KeyError, 'This Subscriber %s provide no list method:%r'\ raise KeyError, 'This Subscriber %s provide no list method:%r'\
% (self.getPath(), list_method_id) % (self.getPath(), list_method_id)
...@@ -1029,21 +1160,47 @@ class SyncMLSubscription(XMLObject): ...@@ -1029,21 +1160,47 @@ class SyncMLSubscription(XMLObject):
return conflict_list return conflict_list
security.declareProtected(Permissions.ModifyPortalContent, security.declareProtected(Permissions.ModifyPortalContent,
'initialiseSynchronization') 'indexSourceData')
def initialiseSynchronization(self): def indexSourceData(self, client=False):
""" """
Set the status of every signature as not_synchronized Index source data into mysql for ensemble comparison
This depends on synchronization type
""" """
if self.getIsActivityEnabled(): # XXX Must check & index signature also (check lenght of BTree against
self.getAndActivateResetSignature() # lenght of data in sql
else: if (client and self.getSyncmlAlertCode() not in \
for signature in self.contentValues(portal_type='SyncML Signature'): ("one_way_from_server", "refresh_from_server_only")) or \
# Change the status only if we are not in a conflict mode (not client and self.getSyncmlAlertCode() not in \
if signature.getValidationState() not in ( ("one_way_from_client", "refresh_from_client_only")):
'conflict',
'conflict_resolved_with_merge', portal = self.getPortalObject()
'conflict_resolved_with_client_command_winning'): # First we must unindex everything
signature.reset() portal.z_unindex_syncml_data(path=self.getSearchableSourcePath())
if self.getIsActivityEnabled():
activate_kw = {
'activity' : 'SQLQueue',
'tag' : self.getRelativeUrl(),
'priority' :ACTIVITY_PRIORITY
}
pref = portal.portal_preferences
if pref.getPreferredSplitIndexation():
kw = {'packet_size' : pref.getPreferredDocumentRetrievedPerActivityCount(),
'activity_count' : pref.getPreferredRetrievalActivityCount()}
else:
kw = {}
self.getAndIndex(
callback="SQLCatalog_indexSyncMLDocumentList",
method_kw={'subscription_path' : self.getRelativeUrl()},
activate_kw=activate_kw,
**kw
)
else:
r = [x.getPath() for x in self.getDocumentList()]
syncml_logger.info("indexing data from %s : %r" %(self.getPath(), r))
portal.SQLCatalog_indexSyncMLDocumentList(
path_list=r[:],
subscription_path=self.getRelativeUrl())
security.declareProtected(Permissions.ModifyPortalContent, security.declareProtected(Permissions.ModifyPortalContent,
'getAndActivateResetSignature') 'getAndActivateResetSignature')
......
...@@ -65,7 +65,7 @@ class SyncMLAsynchronousEngine(EngineMixin): ...@@ -65,7 +65,7 @@ class SyncMLAsynchronousEngine(EngineMixin):
subscription.sendModifications() # Worfklow action subscription.sendModifications() # Worfklow action
syncml_response = None syncml_response = None
tag = subscription_path = subscription.getRelativeUrl() tag = subscription.getRelativeUrl()
# Do action according to synchronization state # Do action according to synchronization state
if subscription.getSynchronizationState() == "initializing": if subscription.getSynchronizationState() == "initializing":
...@@ -75,10 +75,13 @@ class SyncMLAsynchronousEngine(EngineMixin): ...@@ -75,10 +75,13 @@ class SyncMLAsynchronousEngine(EngineMixin):
if subscription.getSyncmlAlertCode() in ("one_way_from_server", if subscription.getSyncmlAlertCode() in ("one_way_from_server",
"refresh_from_server_only"): "refresh_from_server_only"):
# We only get data from server # We only get data from server
syncml_response = self._generateBaseResponse(subscription) syncml_response = subscription.generateBaseResponse()
syncml_response.addFinal() syncml_response.addFinal()
else: else:
self.runGetAndActivate(subscription=subscription, tag=tag) # Make sure it is launched after indexation step
self.runGetAndActivate(subscription=subscription, tag=tag,
after_method_id=("getAndIndex",
"SQLCatalog_indexSyncMLSignatureList"))
syncml_logger.info("X-> Client is sendind modification in activities") syncml_logger.info("X-> Client is sendind modification in activities")
# As we generated all activities to send data at once, process must not # 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 # go back here, go into processing state thus status will be applied and
...@@ -99,7 +102,7 @@ class SyncMLAsynchronousEngine(EngineMixin): ...@@ -99,7 +102,7 @@ class SyncMLAsynchronousEngine(EngineMixin):
% (len(syncml_request.sync_command_list))) % (len(syncml_request.sync_command_list)))
if syncml_request.isFinal: if syncml_request.isFinal:
if not syncml_response: if not syncml_response:
syncml_response = self._generateBaseResponse(subscription) syncml_response = subscription.generateBaseResponse()
# We got and process all sync command from server # We got and process all sync command from server
# notify it that all modifications were applied # notify it that all modifications were applied
syncml_response.addFinal() syncml_response.addFinal()
...@@ -163,10 +166,10 @@ class SyncMLAsynchronousEngine(EngineMixin): ...@@ -163,10 +166,10 @@ class SyncMLAsynchronousEngine(EngineMixin):
# Apply command & send modifications # Apply command & send modifications
# Apply status about object send & synchronized if any # Apply status about object send & synchronized if any
sync_status_counter = self._readStatusList(syncml_request, subscriber, self._readStatusList(syncml_request, subscriber,
generate_alert=True) generate_alert=True)
syncml_response = None syncml_response = None
tag = subscription_path = subscriber.getRelativeUrl() tag = subscriber.getRelativeUrl()
after_method_id = None after_method_id = None
if subscriber.getSynchronizationState() == "sending_modifications": if subscriber.getSynchronizationState() == "sending_modifications":
if syncml_request.isFinal: if syncml_request.isFinal:
...@@ -202,15 +205,13 @@ class SyncMLAsynchronousEngine(EngineMixin): ...@@ -202,15 +205,13 @@ class SyncMLAsynchronousEngine(EngineMixin):
if syncml_request.isFinal: if syncml_request.isFinal:
# Server then sends its modifications # Server then sends its modifications
subscriber.sendModifications() subscriber.sendModifications()
# Now that everything is ok, init sync information # Run indexation only once client have sent its modifications
if subscriber.getSyncmlAlertCode() not in ("one_way_from_client", subscriber.indexSourceData()
"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 # Start to send modification only once we have processed
# all message from client # all message from client
after_method_id='processServerSynchronization', after_method_id=('processServerSynchronization',
'SQLCatalog_indexSyncMLDocumentList')
# XXX after tag might also be required to make sure all data are indexed
tag = (tag, "%s_reset" % subscriber.getPath(),) tag = (tag, "%s_reset" % subscriber.getPath(),)
# Do not continue in elif, as sending modifications is done in the same # Do not continue in elif, as sending modifications is done in the same
# package as sending notifications # package as sending notifications
...@@ -230,7 +231,7 @@ class SyncMLAsynchronousEngine(EngineMixin): ...@@ -230,7 +231,7 @@ class SyncMLAsynchronousEngine(EngineMixin):
# Server has no modification to send to client, return final message # Server has no modification to send to client, return final message
syncml_logger.info("X-> Server sending final message") syncml_logger.info("X-> Server sending final message")
if not syncml_response: if not syncml_response:
syncml_response = self._generateBaseResponse(subscriber) syncml_response = subscriber.generateBaseResponse()
syncml_response.addFinal() syncml_response.addFinal()
if subscriber.getSynchronizationState() == "finished": if subscriber.getSynchronizationState() == "finished":
...@@ -242,10 +243,9 @@ class SyncMLAsynchronousEngine(EngineMixin): ...@@ -242,10 +243,9 @@ class SyncMLAsynchronousEngine(EngineMixin):
after_tag=tag).sendMessage( after_tag=tag).sendMessage(
xml=str(syncml_response)) xml=str(syncml_response))
def runGetAndActivate(self, subscription, tag, after_method_id=None): def runGetAndActivate(self, subscription, tag, after_method_id=None):
""" """
Generate tag and method parameter and call the getAndActivate method Launch the browsing of GID that will call the generation of syncml commands
""" """
activate_kw = { activate_kw = {
'activity' : 'SQLQueue', 'activity' : 'SQLQueue',
...@@ -253,20 +253,20 @@ class SyncMLAsynchronousEngine(EngineMixin): ...@@ -253,20 +253,20 @@ class SyncMLAsynchronousEngine(EngineMixin):
'tag' :tag, 'tag' :tag,
'priority' :ACTIVITY_PRIORITY 'priority' :ACTIVITY_PRIORITY
} }
method_kw = {
'subscription_path' : subscription.getRelativeUrl(),
}
pref = getSite().portal_preferences pref = getSite().portal_preferences
count = subscription.getAndActivate( subscription.getAndActivate(
callback="sendSyncCommand", callback="sendSyncCommand",
method_kw=method_kw,
activate_kw=activate_kw, activate_kw=activate_kw,
packet_size=pref.getPreferredDocumentRetrievedPerActivityCount(), packet_size=pref.getPreferredDocumentRetrievedPerActivityCount(),
activity_count=pref.getPreferredRetrievalActivityCount(), activity_count=pref.getPreferredRetrievalActivityCount(),
) )
# Then get deleted document # then send the final message of this sync part
# this will send also the final message of this sync part if pref.getPreferredCheckDeleteAtEnd():
subscription.activate(after_tag=tag)._getDeletedData() subscription.activate(after_tag=tag,
priority=ACTIVITY_PRIORITY+1).getDeletedSyncMLData()
else:
subscription.activate(after_tag=tag,
priority=ACTIVITY_PRIORITY+1)._sendFinalMessage()
return True return True
...@@ -284,9 +284,9 @@ class SyncMLAsynchronousEngine(EngineMixin): ...@@ -284,9 +284,9 @@ class SyncMLAsynchronousEngine(EngineMixin):
response_id_list = [None for x in response_id_list = [None for x in
xrange(len(syncml_request.sync_command_list))] xrange(len(syncml_request.sync_command_list))]
split = getSite().portal_preferences.getPreferredSyncActionPerActivityCount() split = getSite().portal_preferences.getPreferredSyncActionPerActivityCount()
if not split: if not split: # We do not use activities
if send_response: if send_response:
syncml_response = self._generateBaseResponse(subscription) syncml_response = subscription.generateBaseResponse()
else: else:
syncml_response = None syncml_response = None
subscription.applyActionList(syncml_request, syncml_response) subscription.applyActionList(syncml_request, syncml_response)
...@@ -295,10 +295,9 @@ class SyncMLAsynchronousEngine(EngineMixin): ...@@ -295,10 +295,9 @@ class SyncMLAsynchronousEngine(EngineMixin):
activity="SQLQueue", activity="SQLQueue",
priority=ACTIVITY_PRIORITY, priority=ACTIVITY_PRIORITY,
tag=subscription.getRelativeUrl()).sendMessage(xml=str(syncml_response)) tag=subscription.getRelativeUrl()).sendMessage(xml=str(syncml_response))
else: else:
# XXX For now always split by one # XXX For now always split by one
activate = subscription.getPortalObject().portal_synchronizations.activate activate = subscription.activate
activate_kw = { activate_kw = {
"activity" :"SQLQueue", "activity" :"SQLQueue",
"priority" : ACTIVITY_PRIORITY, "priority" : ACTIVITY_PRIORITY,
...@@ -309,10 +308,9 @@ class SyncMLAsynchronousEngine(EngineMixin): ...@@ -309,10 +308,9 @@ class SyncMLAsynchronousEngine(EngineMixin):
for action in syncml_request.sync_command_list: for action in syncml_request.sync_command_list:
syncml_logger.info("---> launch action in activity %s" %(action,)) syncml_logger.info("---> launch action in activity %s" %(action,))
activate(**activate_kw).applySyncCommand( activate(**activate_kw).applySyncCommand(
subscription_path=subscription.getRelativeUrl(),
response_message_id=response_id_list.pop(), response_message_id=response_id_list.pop(),
activate_kw=activate_kw, activate_kw=activate_kw,
action=action, action=action,
request_message_id=syncml_request.header["message_id"], request_message_id=syncml_request.header["message_id"],
simulate=False) simulate=False)
# XXX Response is not send here # Response is sent by the activity
...@@ -46,15 +46,6 @@ class EngineMixin(object): ...@@ -46,15 +46,6 @@ class EngineMixin(object):
security = ClassSecurityInfo() 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') security.declarePrivate('_readStatusList')
def _readStatusList(self, syncml_request, domain, syncml_response=None, def _readStatusList(self, syncml_request, domain, syncml_response=None,
...@@ -63,6 +54,7 @@ class EngineMixin(object): ...@@ -63,6 +54,7 @@ class EngineMixin(object):
Read status (answer to command) and act according to them Read status (answer to command) and act according to them
""" """
sync_status_counter = 0 sync_status_counter = 0
path_list = []
for status in syncml_request.status_list: for status in syncml_request.status_list:
if status["command"] == "SyncHdr": # Check for authentication if status["command"] == "SyncHdr": # Check for authentication
if domain.getSynchronizationState() != "initializing": if domain.getSynchronizationState() != "initializing":
...@@ -83,7 +75,7 @@ class EngineMixin(object): ...@@ -83,7 +75,7 @@ class EngineMixin(object):
status['authentication_type'])) status['authentication_type']))
# XXX Not working To Review ! # XXX Not working To Review !
raise NotImplementedError("Adding credentials") raise NotImplementedError("Adding credentials")
syncml_response = self._generateBaseResponse(domain) syncml_response = domain.generateBaseResponse()
syncml_response.addCredentialMessage(domain) syncml_response.addCredentialMessage(domain)
return syncml_response return syncml_response
elif status['status_code'] == \ elif status['status_code'] == \
...@@ -124,7 +116,7 @@ class EngineMixin(object): ...@@ -124,7 +116,7 @@ class EngineMixin(object):
'conflict_resolved_with_merge'): 'conflict_resolved_with_merge'):
# We will have to apply the update, and we should not care # We will have to apply the update, and we should not care
# about conflicts, so we have to force the update # about conflicts, so we have to force the update
signature.drift() signature.noConflict()
signature.setForce(True) signature.setForce(True)
syncml_logger.error("\tObject merged %s" % syncml_logger.error("\tObject merged %s" %
(status['source'] or status['target'])) (status['source'] or status['target']))
...@@ -134,20 +126,25 @@ class EngineMixin(object): ...@@ -134,20 +126,25 @@ class EngineMixin(object):
'conflict_resolved_with_client_command_winning')): 'conflict_resolved_with_client_command_winning')):
syncml_logger.error("\tObject synchronized %s" % syncml_logger.error("\tObject synchronized %s" %
(status['source'] or status['target'],)) (status['source'] or status['target'],))
if signature.getValidationState() != "no_conflict":
signature.noConflict()
signature.synchronize() signature.synchronize()
elif status['status_code'] == resolveSyncmlStatusCode('chunk_accepted'): elif status['status_code'] == resolveSyncmlStatusCode('chunk_accepted'):
syncml_logger.info("Chunk was accepted for %s" % (object_gid,)) syncml_logger.info("Chunk was accepted for %s" % (object_gid,))
else: else:
raise ValueError("Unknown status code : %r" % (status['status_code'],)) raise ValueError("Unknown status code : %r" % (status['status_code'],))
# Index signature now to fill the data column
path_list.append(signature.getPath())
elif status['command'] == 'Delete': elif status['command'] == 'Delete':
sync_status_counter += 1 sync_status_counter += 1
object_gid = status['source'] or status['target'] object_gid = status['source'] or status['target']
signature = domain.getSignatureFromGid(object_gid) signature = domain.getSignatureFromGid(object_gid)
if status['status_code'] == resolveSyncmlStatusCode('success'): if status['status_code'] == resolveSyncmlStatusCode('success'):
if signature: if signature:
domain.z_delete_data_from_path(path=signature.getPath())
domain._delObject(signature.getId()) domain._delObject(signature.getId())
else: else:
raise ValueError("Found no signature to delete") raise ValueError("Found no signature to delete for gid %s" %(object_gid,))
else: else:
raise ValueError("Unknown status code : %r" % (status['status_code'],)) raise ValueError("Unknown status code : %r" % (status['status_code'],))
syncml_logger.error("\tObject deleted %s" % syncml_logger.error("\tObject deleted %s" %
...@@ -155,6 +152,8 @@ class EngineMixin(object): ...@@ -155,6 +152,8 @@ class EngineMixin(object):
else: else:
raise ValueError("Unknown status command : %r" % (status['command'],)) raise ValueError("Unknown status command : %r" % (status['command'],))
if len(path_list):
domain.SQLCatalog_indexSyncMLDocumentList(path_list)
return sync_status_counter return sync_status_counter
# #
...@@ -191,10 +190,8 @@ class EngineMixin(object): ...@@ -191,10 +190,8 @@ class EngineMixin(object):
if subscription.getAuthenticationState() != 'logged_in': if subscription.getAuthenticationState() != 'logged_in':
# Workflow action # Workflow action
subscription.login() subscription.login()
if subscription.getSyncmlAlertCode() not in ("one_way_from_server",
"refresh_from_server_only"): subscription.indexSourceData(client=True)
# Reset signature only if client send its modification to server
subscription.initialiseSynchronization()
# Create the package 1 # Create the package 1
syncml_response = SyncMLResponse() syncml_response = SyncMLResponse()
...@@ -301,7 +298,9 @@ class EngineMixin(object): ...@@ -301,7 +298,9 @@ class EngineMixin(object):
'one_way_from_server', 'one_way_from_server',
'refresh_from_client_only', 'refresh_from_client_only',
'one_way_from_client'): 'one_way_from_client'):
# XXX Why re-editing here ? # Make sure we update configuration based on publication data
# so that manual edition is propagated
# XXX Must check all properties that must be setted
subscriber.setXmlBindingGeneratorMethodId( subscriber.setXmlBindingGeneratorMethodId(
publication.getXmlBindingGeneratorMethodId()) publication.getXmlBindingGeneratorMethodId())
subscriber.setConduitModuleId(publication.getConduitModuleId()) subscriber.setConduitModuleId(publication.getConduitModuleId())
......
...@@ -54,7 +54,7 @@ class SyncMLSynchronousEngine(EngineMixin): ...@@ -54,7 +54,7 @@ class SyncMLSynchronousEngine(EngineMixin):
# Must check what server tell about database synchronization # Must check what server tell about database synchronization
# and update the mode if required # and update the mode if required
syncml_response = self._generateBaseResponse(subscription) syncml_response = subscription.generateBaseResponse()
# Read & apply status about databases & synchronizations # Read & apply status about databases & synchronizations
try: try:
...@@ -80,16 +80,16 @@ class SyncMLSynchronousEngine(EngineMixin): ...@@ -80,16 +80,16 @@ class SyncMLSynchronousEngine(EngineMixin):
"refresh_from_server_only"): "refresh_from_server_only"):
# We only get data from server # We only get data from server
finished = True finished = True
syncml_response.addFinal()
else: else:
finished = subscription._getSyncMLData( finished = subscription._getSyncMLData(syncml_response=syncml_response,
syncml_response=syncml_response, min_gid=None, max_gid=None)
) if finished:
syncml_logger.info("-> Client sendind modification, finished %s" % (finished,)) # Delete message will contain final tag
subscription.getDeletedSyncMLData(syncml_response=syncml_response)
syncml_logger.info("-> Client sendind modification, finished %s" % (finished,))
if 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 # Will then start processing sync commands from server
subscription.processSyncRequest() subscription.processSyncRequest()
...@@ -149,7 +149,8 @@ class SyncMLSynchronousEngine(EngineMixin): ...@@ -149,7 +149,8 @@ class SyncMLSynchronousEngine(EngineMixin):
raise ValueError("Authentication failed, impossible to sync data") raise ValueError("Authentication failed, impossible to sync data")
# Apply command & send modifications # Apply command & send modifications
syncml_response = self._generateBaseResponse(subscriber) # XXX This can be called on subscription instead
syncml_response = subscriber.generateBaseResponse()
# Apply status about object send & synchronized if any # Apply status about object send & synchronized if any
self._readStatusList(syncml_request, subscriber, syncml_response, True) self._readStatusList(syncml_request, subscriber, syncml_response, True)
...@@ -191,10 +192,8 @@ class SyncMLSynchronousEngine(EngineMixin): ...@@ -191,10 +192,8 @@ class SyncMLSynchronousEngine(EngineMixin):
if syncml_request.isFinal: if syncml_request.isFinal:
# Server will now send its modifications # Server will now send its modifications
subscriber.sendModifications() subscriber.sendModifications()
if subscriber.getSyncmlAlertCode() not in ("one_way_from_client", # Run indexation only once client has sent its modifications
"refresh_from_client_only"): subscriber.indexSourceData()
# 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 # Do not continue in elif, as sending modifications is done in the same
# package as sending notifications # package as sending notifications
...@@ -204,13 +203,16 @@ class SyncMLSynchronousEngine(EngineMixin): ...@@ -204,13 +203,16 @@ class SyncMLSynchronousEngine(EngineMixin):
"refresh_from_client_only"): "refresh_from_client_only"):
# We only get data from client # We only get data from client
finished = True finished = True
syncml_response.addFinal()
else: else:
finished = subscriber._getSyncMLData( finished = subscriber._getSyncMLData(syncml_response=syncml_response,
syncml_response=syncml_response) min_gid=None, max_gid=None)
if finished:
# Delete message will contain final tag
subscriber.getDeletedSyncMLData(syncml_response=syncml_response)
syncml_logger.info("-> Server sendind data, finished %s" % (finished,)) syncml_logger.info("-> Server sendind data, finished %s" % (finished,))
if finished: if finished:
subscriber._getDeletedData(syncml_response=syncml_response)
syncml_response.addFinal()
subscriber.waitNotifications() subscriber.waitNotifications()
# Do not go into finished here as we must wait for # Do not go into finished here as we must wait for
# notifications from client # notifications from client
......
...@@ -28,51 +28,14 @@ ...@@ -28,51 +28,14 @@
############################################################################## ##############################################################################
# Namespaces.
SYNCML_NAMESPACE = 'SYNCML:SYNCML1.2'
# In SyncML Representation Protocol OMA
# we use URN as format of namespace
# List namespaces supported
URN_LIST = ('SYNCML:SYNCML1.1', 'SYNCML:SYNCML1.2')
SYNCML_NAMESPACE = 'SYNCML:SYNCML1.2'
NSMAP = {'syncml': SYNCML_NAMESPACE} NSMAP = {'syncml': SYNCML_NAMESPACE}
## SyncML Alert Codes
#TWO_WAY = 200
#SLOW_SYNC = 201 # This means we get the data from the publication
#ONE_WAY_FROM_SERVER = 204
#CODE_LIST = (TWO_WAY, ONE_WAY_FROM_SERVER,)
# SyncML Status Codes
#SUCCESS = 200
#ITEM_ADDED = 201
#WAITING_DATA = 214
#REFRESH_REQUIRED = 508
#CHUNK_OK = 214
#CONFLICT = 409 # A conflict is detected
#CONFLICT_MERGE = 207 # We have merged the two versions, sending
## whatever is needed to change(replace)
#CONFLICT_CLIENT_WIN = 208 # The client is the "winner", we keep
## the version of the client
#UNAUTHORIZED = 401
#AUTH_REQUIRED = 407
#AUTH_ACCEPTED = 212
NULL_ANCHOR = '00000000T000000Z' NULL_ANCHOR = '00000000T000000Z'
# ERP5 Sync Codes for Signatures
SYNCHRONIZED = 1
#SENT = 2
#NOT_SENT = 3
PARTIAL = 4
NOT_SYNCHRONIZED = 5
PUB_CONFLICT_MERGE = 6
PUB_CONFLICT_CLIENT_WIN = 8
#MAX_LINES = 5000
MAX_OBJECTS = 300 MAX_OBJECTS = 300
MAX_LEN = 1<<16 MAX_LEN = 1<<16
MAX_DOCUMENT_PER_MESSAGE = 2
XUPDATE_INSERT_LIST = ('xupdate:insert-after', 'xupdate:insert-before') XUPDATE_INSERT_LIST = ('xupdate:insert-after', 'xupdate:insert-before')
XUPDATE_ADD = 'xupdate:append' XUPDATE_ADD = 'xupdate:append'
...@@ -84,20 +47,7 @@ XUPDATE_INSERT_OR_ADD_LIST = XUPDATE_INSERT_LIST + (XUPDATE_ADD,) ...@@ -84,20 +47,7 @@ XUPDATE_INSERT_OR_ADD_LIST = XUPDATE_INSERT_LIST + (XUPDATE_ADD,)
ADD_ACTION = 'Add' ADD_ACTION = 'Add'
REPLACE_ACTION = 'Replace' REPLACE_ACTION = 'Replace'
##media types :
#MEDIA_TYPE = {}
#MEDIA_TYPE['TEXT_XML'] = 'text/xml'
#MEDIA_TYPE['TEXT_VCARD'] = 'text/vcard'
#MEDIA_TYPE['TEXT_XVCARD'] = 'text/x-vcard'
##content types :
#CONTENT_TYPE = {}
#CONTENT_TYPE['SYNCML_XML'] = 'application/vnd.syncml+xml'
#CONTENT_TYPE['SYNCML_WBXML'] = 'application/vnd.syncml+wbxml'
#Activity priority
ACTIVITY_PRIORITY = 5 ACTIVITY_PRIORITY = 5
class SynchronizationError(Exception): class SynchronizationError(Exception):
pass pass
...@@ -579,9 +579,14 @@ class SyncMLRequest(object): ...@@ -579,9 +579,14 @@ class SyncMLRequest(object):
sync_command_kw["xml_data"] = etree.tostring(xml_data[0]) sync_command_kw["xml_data"] = etree.tostring(xml_data[0])
else: else:
# If not xml, return raw data # If not xml, return raw data
# XXX This must be CDATA type
data = sync_command.xpath('string(.//syncml:Item/syncml:Data)',
namespaces=self.data.nsmap)
if isinstance(data, etree.CDATA):
parser = etree.XMLParser(strip_cdata=False)
cdata = etree.XML(data, parser)
data = cdata.text
# XXX this is unicode and can be a problem for activity # XXX this is unicode and can be a problem for activity
sync_command_kw["raw_data"] = sync_command.xpath( sync_command_kw["raw_data"] = data
'string(.//syncml:Item/syncml:Data)',
namespaces=self.data.nsmap)
append(sync_command_kw) append(sync_command_kw)
...@@ -26,7 +26,6 @@ ...@@ -26,7 +26,6 @@
############################################################################## ##############################################################################
from os import path from os import path
from lxml import etree
from logging import getLogger, Formatter from logging import getLogger, Formatter
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
...@@ -36,26 +35,14 @@ from Products.ERP5Type import Permissions ...@@ -36,26 +35,14 @@ from Products.ERP5Type import Permissions
from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5SyncML.SyncMLConstant import ACTIVITY_PRIORITY, \ from Products.ERP5SyncML.SyncMLConstant import ACTIVITY_PRIORITY, \
SynchronizationError SynchronizationError
from Products.ERP5SyncML.SyncMLMessage import SyncMLResponse, SyncMLRequest from Products.ERP5SyncML.SyncMLMessage import SyncMLRequest
from Products.ERP5SyncML.Engine.SynchronousEngine import SyncMLSynchronousEngine from Products.ERP5SyncML.Engine.SynchronousEngine import SyncMLSynchronousEngine
from Products.ERP5SyncML.Engine.AsynchronousEngine import SyncMLAsynchronousEngine from Products.ERP5SyncML.Engine.AsynchronousEngine import SyncMLAsynchronousEngine
from Products.ERP5SyncML.Transport.HTTP import HTTPTransport
from Products.ERP5SyncML.Transport.File import FileTransport
from Products.ERP5SyncML.Transport.Mail import MailTransport
from Products.ERP5.ERP5Site import getSite from Products.ERP5.ERP5Site import getSite
synchronous_engine = SyncMLSynchronousEngine() synchronous_engine = SyncMLSynchronousEngine()
asynchronous_engine = SyncMLAsynchronousEngine() asynchronous_engine = SyncMLAsynchronousEngine()
transport_scheme_dict = {
"http" : HTTPTransport(),
"https" : HTTPTransport(),
"file" : FileTransport(),
"mail" : MailTransport(),
}
parser = etree.XMLParser(remove_blank_text=True)
# Logging channel definitions # Logging channel definitions
# Main logging channel # Main logging channel
syncml_logger = getLogger('ERP5SyncML') syncml_logger = getLogger('ERP5SyncML')
...@@ -390,7 +377,6 @@ class SynchronizationTool(BaseTool): ...@@ -390,7 +377,6 @@ class SynchronizationTool(BaseTool):
return engine.processClientSynchronization(syncml_request, subscription) return engine.processClientSynchronization(syncml_request, subscription)
# Send the message # Send the message
# XXX This must depends on activity enables property, maybe use engine
if subscription.getIsActivityEnabled(): if subscription.getIsActivityEnabled():
subscription.activate( subscription.activate(
after_tag="%s_reset" %(subscription.getPath(),), after_tag="%s_reset" %(subscription.getPath(),),
...@@ -402,79 +388,4 @@ class SynchronizationTool(BaseTool): ...@@ -402,79 +388,4 @@ class SynchronizationTool(BaseTool):
return str(syncml_response) return str(syncml_response)
def applySyncCommand(self, subscription_path, response_message_id,
activate_kw, **kw):
"""
This methods is intented to be called by asynchronous engine in activity to
apply sync commands for a subset of data
As engines are not zodb object, the tool acts as a placeholder for method
that need to be called in activities
"""
subscription = self.restrictedTraverse(subscription_path)
assert subscription is not None, "Impossible to find subscription %s" \
% (subscription_path)
# Build Message
if response_message_id:
syncml_response = SyncMLResponse()
syncml_response.addHeader(
session_id=subscription.getSessionId(),
message_id=response_message_id,
target=subscription.getUrlString(),
source=subscription.getSubscriptionUrlString())
syncml_response.addBody()
else:
syncml_response = None
subscription.applySyncCommand(syncml_response=syncml_response, **kw)
# Send the message in activity to prevent recomputing data in case of
# transport failure
if syncml_response:
syncml_logger("---- %s sending %s notifications of sync"
% (subscription.getTitle(),
syncml_response.sync_confirmation_counter))
subscription.activate(activity="SQLQueue",
# group_method_id=None,
# group_method_cost=.05,
tag=activate_kw).sendMessage(xml=str(syncml_response))
def sendSyncCommand(self, id_list, message_id, subscription_path,
activate_kw, is_final_message=False):
"""
This methods is intented to be called by asynchronous engine in activity to
send sync commands for a subset of data
As engines are not zodb object, the tool acts as a placeholder for method
that need to be called in activities
"""
subscription = self.restrictedTraverse(subscription_path)
assert subscription is not None, "Impossible to find subscription %s" \
% (subscription_path)
# Build Message
syncml_response = SyncMLResponse()
syncml_response.addHeader(
session_id=subscription.getSessionId(),
message_id=message_id,
target=subscription.getUrlString(),
source=subscription.getSubscriptionUrlString())
syncml_response.addBody()
subscription._getSyncMLData(
syncml_response=syncml_response,
id_list=id_list,
)
if is_final_message:
# Notify that all modifications were sent
syncml_response.addFinal()
# Send the message in activity to prevent recomputing data in case of
# transport failure
# activate_kw["group_method_id"] = None
# activate_kw["group_method_cost"] = .05
subscription.activate(**activate_kw).sendMessage(xml=str(syncml_response))
InitializeClass(SynchronizationTool) InitializeClass(SynchronizationTool)
...@@ -118,6 +118,8 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin): ...@@ -118,6 +118,8 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
def afterSetUp(self): def afterSetUp(self):
"""Setup.""" """Setup."""
self.login() self.login()
self.portal.z_drop_syncml()
self.portal.z_create_syncml()
self.addPublications() self.addPublications()
self.addSubscriptions() self.addSubscriptions()
self.portal = self.getPortal() self.portal = self.getPortal()
...@@ -316,12 +318,12 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin): ...@@ -316,12 +318,12 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
for document in document_server.objectValues(): for document in document_server.objectValues():
state_list = self.getSynchronizationState(document) state_list = self.getSynchronizationState(document)
for state in state_list: for state in state_list:
self.assertEqual(state[1], 'synchronized') self.assertEqual(state[1], 'no_conflict')
document_client1 = self.getDocumentClient1() document_client1 = self.getDocumentClient1()
for document in document_client1.objectValues(): for document in document_client1.objectValues():
state_list = self.getSynchronizationState(document) state_list = self.getSynchronizationState(document)
for state in state_list: for state in state_list:
self.assertEqual(state[1], 'synchronized') self.assertEqual(state[1], 'no_conflict')
# Check for each signature that the tempXML is None # Check for each signature that the tempXML is None
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'): for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
for m in sub.contentValues(): for m in sub.contentValues():
...@@ -418,13 +420,7 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin): ...@@ -418,13 +420,7 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
def getTitle(self): def getTitle(self):
return "ERP5 Document SyncML" return "ERP5 Document SyncML"
def setupPublicationAndSubscriptionIdGenerator(self):
portal_sync = self.getSynchronizationTool()
sub1 = portal_sync[self.sub_id1]
pub = portal_sync[self.pub_id]
def checkSynchronizationStateIsConflict(self, portal_type='Text'): def checkSynchronizationStateIsConflict(self, portal_type='Text'):
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer() document_server = self.getDocumentServer()
for document in document_server.objectValues(): for document in document_server.objectValues():
if document.getId()==self.id1: if document.getId()==self.id1:
...@@ -641,7 +637,6 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin): ...@@ -641,7 +637,6 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
recognize objects (because by default, getGid==getId. Here, we will see recognize objects (because by default, getGid==getId. Here, we will see
if it also works with a somewhat strange getGid if it also works with a somewhat strange getGid
""" """
self.setupPublicationAndSubscriptionIdGenerator()
nb_document = self.createDocumentServerList() nb_document = self.createDocumentServerList()
# This will test adding object # This will test adding object
self.synchronize(self.sub_id1) self.synchronize(self.sub_id1)
......
...@@ -98,6 +98,8 @@ class TestERP5SyncMLMixin(TestMixin): ...@@ -98,6 +98,8 @@ class TestERP5SyncMLMixin(TestMixin):
def afterSetUp(self): def afterSetUp(self):
"""Setup.""" """Setup."""
self.login() self.login()
self.portal.z_drop_syncml()
self.portal.z_create_syncml()
# This test creates Person inside Person, so we modifiy type information to # This test creates Person inside Person, so we modifiy type information to
# allow anything inside Person (we'll cleanup on teardown) # allow anything inside Person (we'll cleanup on teardown)
self.getTypesTool().getTypeInfo('Person').filter_content_types = 0 self.getTypesTool().getTypeInfo('Person').filter_content_types = 0
...@@ -228,6 +230,7 @@ class TestERP5SyncMLMixin(TestMixin): ...@@ -228,6 +230,7 @@ class TestERP5SyncMLMixin(TestMixin):
result = portal_sync.processClientSynchronization(subscription.getPath()) result = portal_sync.processClientSynchronization(subscription.getPath())
self.tic() self.tic()
nb_message += 1 nb_message += 1
self.tic()
return nb_message return nb_message
def synchronizeWithBrokenMessage(self, id): def synchronizeWithBrokenMessage(self, id):
...@@ -329,33 +332,33 @@ class TestERP5SyncMLMixin(TestMixin): ...@@ -329,33 +332,33 @@ class TestERP5SyncMLMixin(TestMixin):
for person in person_server.objectValues(): for person in person_server.objectValues():
state_list = self.getSynchronizationState(person) state_list = self.getSynchronizationState(person)
for state in state_list: for state in state_list:
self.assertEquals(state[1], 'synchronized') self.assertEquals(state[1], 'no_conflict')
person_client1 = self.getPersonClient1() person_client1 = self.getPersonClient1()
for person in person_client1.objectValues(): for person in person_client1.objectValues():
state_list = self.getSynchronizationState(person) state_list = self.getSynchronizationState(person)
for state in state_list: for state in state_list:
self.assertEquals(state[1], 'synchronized') self.assertEquals(state[1], 'no_conflict')
person_client2 = self.getPersonClient2() person_client2 = self.getPersonClient2()
for person in person_client2.objectValues(): for person in person_client2.objectValues():
state_list = self.getSynchronizationState(person) state_list = self.getSynchronizationState(person)
for state in state_list: for state in state_list:
self.assertEquals(state[1], 'synchronized') self.assertEquals(state[1], 'no_conflict')
# Check for each signature that the tempXML is None # Check for each signature that the tempXML is None
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'): for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
for m in sub.contentValues(): for m in sub.contentValues():
self.assertEquals(m.getTemporaryData(), None) self.assertEquals(m.getTemporaryData(), None)
self.assertEquals(m.getPartialData(), None) self.assertEquals(m.getPartialData(), None)
self.assertEquals(m.getValidationState(), "synchronized") self.assertEquals(m.getValidationState(), "no_conflict")
for pub in portal_sync.contentValues(portal_type='SyncML Publication'): for pub in portal_sync.contentValues(portal_type='SyncML Publication'):
for sub in pub.contentValues(portal_type='SyncML Subscription'): for sub in pub.contentValues(portal_type='SyncML Subscription'):
for m in sub.contentValues(): for m in sub.contentValues():
self.assertEquals(m.getPartialData(), None) self.assertEquals(m.getPartialData(), None)
self.assertEquals(m.getValidationState(), "synchronized") self.assertEquals(m.getValidationState(), "no_conflict")
def verifyFirstNameAndLastNameAreNotSynchronized(self, first_name, def verifyFirstNameAndLastNameAreNotSynchronized(self, first_name,
last_name, person_server, person_client): last_name, person_server, person_client):
""" """
verify that the first and last name are NOT synchronized verify that the first and last name are NOT no_conflict
""" """
self.assertNotEqual(person_server.getFirstName(), first_name) self.assertNotEqual(person_server.getFirstName(), first_name)
self.assertNotEqual(person_server.getLastName(), last_name) self.assertNotEqual(person_server.getLastName(), last_name)
...@@ -481,7 +484,6 @@ class TestERP5SyncML(TestERP5SyncMLMixin): ...@@ -481,7 +484,6 @@ class TestERP5SyncML(TestERP5SyncMLMixin):
pub.setConduitModuleId('ERP5ConduitTitleGid') pub.setConduitModuleId('ERP5ConduitTitleGid')
def checkSynchronizationStateIsConflict(self): def checkSynchronizationStateIsConflict(self):
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer() person_server = self.getPersonServer()
for person in person_server.objectValues(): for person in person_server.objectValues():
if person.getId()==self.id1: if person.getId()==self.id1:
...@@ -751,7 +753,6 @@ return [context[%r]] ...@@ -751,7 +753,6 @@ return [context[%r]]
# We will try to get the state of objects # We will try to get the state of objects
# that are just synchronized # that are just synchronized
self.test_08_FirstSynchronization() self.test_08_FirstSynchronization()
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer() person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1) person1_s = person_server._getOb(self.id1)
state_list_s = self.getSynchronizationState(person1_s) state_list_s = self.getSynchronizationState(person1_s)
...@@ -782,6 +783,8 @@ return [context[%r]] ...@@ -782,6 +783,8 @@ return [context[%r]]
kw = {'first_name':self.first_name1,'last_name':self.last_name1} kw = {'first_name':self.first_name1,'last_name':self.last_name1}
person1_c.edit(**kw) person1_c.edit(**kw)
#person1_c.setModificationDate(DateTime()+1) #person1_c.setModificationDate(DateTime()+1)
# import ipdb
# ipdb.set_trace()
self.synchronize(self.sub_id1) self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized() self.checkSynchronizationStateIsSynchronized()
person1_s = person_server._getOb(self.id1) person1_s = person_server._getOb(self.id1)
...@@ -1543,7 +1546,7 @@ return [context[%r]] ...@@ -1543,7 +1546,7 @@ return [context[%r]]
self.assertEquals(client_person.getLastName(), self.last_name1) self.assertEquals(client_person.getLastName(), self.last_name1)
# reset for refresh sync # reset for refresh sync
# after synchronize, the client object retrieve value of server # after synchronization, the client retrieves value from server
self.resetSignaturePublicationAndSubscription() self.resetSignaturePublicationAndSubscription()
self.synchronize(self.sub_id1) self.synchronize(self.sub_id1)
...@@ -1596,7 +1599,7 @@ return [context[%r]] ...@@ -1596,7 +1599,7 @@ return [context[%r]]
publication = self.addPublication() publication = self.addPublication()
self.addRefreshFormClientOnlySubscription() self.addRefreshFormClientOnlySubscription()
nb_person = self.populatePersonClient1() self.populatePersonClient1()
portal_sync = self.getSynchronizationTool() portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync[self.sub_id1] subscription1 = portal_sync[self.sub_id1]
self.assertEquals(subscription1.getSyncmlAlertCode(), self.assertEquals(subscription1.getSyncmlAlertCode(),
......
...@@ -28,9 +28,7 @@ ...@@ -28,9 +28,7 @@
# #
############################################################################## ##############################################################################
from testERP5SyncML import TestERP5SyncMLMixin from testERP5SyncML import TestERP5SyncMLMixin
from zLOG import LOG
class TestERP5SyncMLVCard(TestERP5SyncMLMixin): class TestERP5SyncMLVCard(TestERP5SyncMLMixin):
...@@ -45,6 +43,10 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin): ...@@ -45,6 +43,10 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin):
""" """
return ('erp5_base', 'erp5_syncml',) return ('erp5_base', 'erp5_syncml',)
def afterSetUp(self):
self.portal.z_drop_syncml()
self.portal.z_create_syncml()
def getTitle(self): def getTitle(self):
return 'testERP5SyncMLVCard' return 'testERP5SyncMLVCard'
......
...@@ -69,15 +69,15 @@ class ERP5NodeConduit(TioSafeBaseConduit): ...@@ -69,15 +69,15 @@ class ERP5NodeConduit(TioSafeBaseConduit):
we can filter person based on the plugin they came from we can filter person based on the plugin they came from
""" """
site = self.getIntegrationSite(kw['domain']) site = self.getIntegrationSite(kw['domain'])
default_stc = site.getSourceTrade()
# try to find the corresponding STC # try to find the corresponding STC
stc_list = object.getPortalObject().sale_trade_condition_module.searchFolder(title="%s %s" %(site.getReference(), object.getTitle()), stc_list = object.getPortalObject().sale_trade_condition_module.searchFolder(
validation_state="validated" title="%s %s" %(site.getReference(), object.getTitle()),
) validation_state="validated")
if len(stc_list) == 0: if len(stc_list) == 0:
self._createSaleTradeCondition(object, **kw) self._createSaleTradeCondition(object, **kw)
elif len(stc_list) > 1: elif len(stc_list) > 1:
raise ValueError, "Multiple trade condition (%s) retrieve for %s" %([x.path for x in stc_list], object.getTitle()) raise ValueError, "Multiple trade condition (%s) retrieved for %s" \
% ([x.path for x in stc_list], object.getTitle())
else: else:
stc = stc_list[0].getObject() stc = stc_list[0].getObject()
stc.edit( stc.edit(
...@@ -519,7 +519,10 @@ class ERP5NodeConduit(TioSafeBaseConduit): ...@@ -519,7 +519,10 @@ class ERP5NodeConduit(TioSafeBaseConduit):
elif tag == "email": elif tag == "email":
current_value = str(document.getDefaultEmailText("")) current_value = str(document.getDefaultEmailText(""))
else: else:
current_value = getattr(document, tag) try:
current_value = getattr(document, tag)
except AttributeError:
current_value = None
if current_value: if current_value:
current_value = current_value.encode('utf-8') current_value = current_value.encode('utf-8')
......
...@@ -33,6 +33,7 @@ from Products.ERP5Type.Core.Folder import Folder ...@@ -33,6 +33,7 @@ from Products.ERP5Type.Core.Folder import Folder
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet from Products.ERP5Type import Permissions, PropertySheet
from zLOG import LOG, INFO, ERROR, WARNING from zLOG import LOG, INFO, ERROR, WARNING
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
class IntegrationSite(Folder): class IntegrationSite(Folder):
...@@ -187,7 +188,12 @@ class IntegrationSite(Folder): ...@@ -187,7 +188,12 @@ class IntegrationSite(Folder):
base_mapping = the base property mapping base_mapping = the base property mapping
property = string of the property we want the mapping property = string of the property we want the mapping
""" """
mapping_line = base_mapping.searchFolder(portal_type='Integration Property Mapping', tv = getTransactionalVariable()
key = "%s-%s" % (base_mapping.getPath(), property_name)
try:
mapping_line = tv[key]
except KeyError:
tv[key] = mapping_line = base_mapping.searchFolder(portal_type='Integration Property Mapping',
path = "%s%%" %(base_mapping.getPath()), path = "%s%%" %(base_mapping.getPath()),
destination_reference=property_name, destination_reference=property_name,
) )
......
...@@ -854,14 +854,12 @@ def setDefaultClassProperties(property_holder): ...@@ -854,14 +854,12 @@ def setDefaultClassProperties(property_holder):
) )
} }
from Globals import Persistent, PersistentMapping
def importLocalDocument(class_id, path=None, class_path=None): def importLocalDocument(class_id, path=None, class_path=None):
"""Imports a document class and registers it in ERP5Type Document """Imports a document class and registers it in ERP5Type Document
repository ( Products.ERP5Type.Document ) repository ( Products.ERP5Type.Document )
""" """
import Products.ERP5Type.Document import Products.ERP5Type.Document
import Permissions
if class_path: if class_path:
assert path is None assert path is None
......
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