Commit 1ef01e23 authored by Sebastien Robin's avatar Sebastien Robin

many updates


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@227 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 3484454e
......@@ -37,6 +37,8 @@ from xml.dom.ext.reader.Sax2 import FromXml
from DateTime.DateTime import DateTime
from email.MIMEBase import MIMEBase
from email import Encoders
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
import pickle
import string
from xml.dom.ext import PrettyPrint
......@@ -84,16 +86,21 @@ class ERP5Conduit(XMLSyncUtilsMixin):
"""
# Declarative security
security = ClassSecurityInfo()
security.declareProtected(Permissions.AccessContentsInformation,'getEncoding')
def getEncoding(self):
"""
return the string corresponding to the local encoding
"""
return "iso-8859-1"
security.declareProtected(Permissions.ModifyPortalContent, '__init__')
def __init__(self):
self.args = {}
security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
def addNode(self, xml=None, object=None, previous_xml=None, force=0, **kw):
"""
A node is added
......@@ -117,11 +124,13 @@ class ERP5Conduit(XMLSyncUtilsMixin):
LOG('addNode',0,'object.id: %s' % object.getId())
LOG('addNode',0,'xml.nodeName: %s' % xml.nodeName)
LOG('addNode',0,'isSubObjectAdd: %i' % self.getSubObjectDepth(xml))
LOG('addNode',0,'isHistoryAdd: %i' % self.isHistoryAdd(xml))
if xml.nodeName in self.XUPDATE_INSERT_OR_ADD and self.getSubObjectDepth(xml)==0:
for element in self.getXupdateElementList(xml):
xml = self.getElementFromXupdate(element)
conflict_list += self.addNode(xml=xml,object=object,
previous_xml=previous_xml, force=force, **kw)
if self.isHistoryAdd(xml)!=-1: # bad hack XXX to be removed
for element in self.getXupdateElementList(xml):
xml = self.getElementFromXupdate(element)
conflict_list += self.addNode(xml=xml,object=object,
previous_xml=previous_xml, force=force, **kw)
elif xml.nodeName == 'object':
object_id = self.getAttribute(xml,'id')
docid = self.getObjectDocid(xml)
......@@ -184,25 +193,28 @@ class ERP5Conduit(XMLSyncUtilsMixin):
conflict_list += self.addNode(xml=sub_xml,object=sub_object,
previous_xml=sub_previous_xml, force=force)
elif xml.nodeName == self.history_tag or self.isHistoryAdd(xml)>0:
#return conflict_list # XXX to be removed soon
# We want to add a workflow action
wf_tool = getToolByName(object,'portal_workflow')
wf_id = self.getAttribute(xml,'id')
if wf_id is None: # History added by xupdate
wf_id = self.getHistoryIdFromSelect(xml)
xml = self.getElementNodeList(xml)[0]
# xml = self.getElementFromXupdate(xml) # This doesn't works, because with subnodes,
# not only text as before
LOG('addNode, workflow_history id:',0,wf_id)
LOG('addNode, workflow_history xml:',0,xml)
for action in self.getWorkflowActionFromXml(xml):
status = self.getStatusFromXml(action)
LOG('addNode, status:',0,status)
#for action in self.getWorkflowActionFromXml(xml):
status = self.getStatusFromXml(xml)
LOG('addNode, status:',0,status)
wf_conflict_list = self.isWorkflowActionAddable(object=object,
status=status,wf_tool=wf_tool,
xml=xml)
LOG('addNode, workflow_history wf_conflict_list:',0,wf_conflict_list)
if wf_conflict_list==[] or force:
LOG('addNode, setting status:',0,'ok')
wf_tool.setStatusOf(wf_id,object,status)
else:
conflict_list += wf_conflict_list
elif xml.nodeName in self.local_role_list:
#return conflict_list # XXX to be removed soon
# We want to add a local role
#user = self.getParameter(xml,'user')
roles = self.convertXmlValue(xml.childNodes[0].data,data_type='tokens')
roles = list(roles) # Needed for CPS, or we have a CPS error
user = roles[0]
......@@ -212,6 +224,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
conflict_list += self.updateNode(xml=xml,object=object, force=force, **kw)
return conflict_list
security.declareProtected(Permissions.ModifyPortalContent, 'deleteNode')
def deleteNode(self, xml=None, object=None, object_id=None, force=None, **kw):
"""
A node is deleted
......@@ -246,6 +259,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
pass
return conflict_list
security.declareProtected(Permissions.ModifyPortalContent, 'updateNode')
def updateNode(self, xml=None, object=None, previous_xml=None, force=0, **kw):
"""
A node is updated with some xupdate
......@@ -257,6 +271,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
conflict_list = []
xml = self.convertToXml(xml)
LOG('updateNode',0,'xml.nodeName: %s' % xml.nodeName)
LOG('updateNode, force: ',0,force)
# we have an xupdate xml
if xml.nodeName == 'xupdate:modifications':
#xupdate_utils = XupdateUtils()
......@@ -310,7 +325,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
# - old_data : the data from this box but at the time of the last synchronization
# - current_data : the data actually on this box
isConflict = 0
if previous_xml is not None: # if no previous_xml, no conflict
if (previous_xml is not None) and (not force): # if no previous_xml, no conflict
old_data = self.getObjectProperty(keyword,previous_xml,data_type=data_type)
current_data = object.getProperty(keyword)
LOG('updateNode',0,'Conflict data: %s' % str(data))
......@@ -326,8 +341,12 @@ class ERP5Conduit(XMLSyncUtilsMixin):
isConflict = 1
string_io = StringIO()
PrettyPrint(xml,stream=string_io)
conflict = Conflict(object_path=object.getPhysicalPath())
conflict = Conflict(object_path=object.getPhysicalPath(),
keyword=keyword)
conflict.setXupdate(string_io.getvalue())
if not (data_type in self.binary_type_list):
conflict.setLocalValue(current_data)
conflict.setRemoteValue(data)
conflict_list += [conflict]
#conflict_list += [Conflict(object_path=object.getPhysicalPath(),
# keyword=keyword,
......@@ -381,6 +400,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
previous_xml=sub_previous_xml)
return conflict_list
security.declareProtected(Permissions.AccessContentsInformation,'getFormatedArgs')
def getFormatedArgs(self, args=None):
"""
This lookd inside the args dictionnary and then
......@@ -412,6 +432,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
new_args[keyword] = data
return new_args
security.declareProtected(Permissions.AccessContentsInformation,'isProperty')
def isProperty(self, xml):
"""
Check if it is a simple property
......@@ -425,6 +446,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return 0
return 1
security.declareProtected(Permissions.AccessContentsInformation,'getSubObjectXupdate')
def getSubObjectXupdate(self, xml):
"""
This will change the xml in order to change the update
......@@ -436,6 +458,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
subnode.nodeValue = self.getSubObjectSelect(subnode.nodeValue)
return xml
security.declareProtected(Permissions.AccessContentsInformation,'isHistoryAdd')
def isHistoryAdd(self, xml):
bad_list = (self.history_exp)
for subnode in self.getAttributeNodeList(xml):
......@@ -443,9 +466,13 @@ class ERP5Conduit(XMLSyncUtilsMixin):
value = subnode.nodeValue
for bad_string in bad_list:
if re.search(bad_string,value) is not None:
return 1
if re.search(self.bad_history_exp,value) is None:
return 1
else:
return -1
return 0
security.declareProtected(Permissions.AccessContentsInformation,'isSubObjectModification')
def isSubObjectModification(self, xml):
"""
Check if it is a modification from an subobject
......@@ -460,6 +487,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return 1
return 0
security.declareProtected(Permissions.AccessContentsInformation,'getSubObjectDepth')
def getSubObjectDepth(self, xml):
"""
Give the Depth of a subobject modification
......@@ -490,6 +518,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return (1 - i)
return 0
security.declareProtected(Permissions.AccessContentsInformation,'getSubObjectSelect')
def getSubObjectSelect(self, select):
"""
Return a string wich is the selection for the subobject
......@@ -505,6 +534,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
select = new_value
return select
security.declareProtected(Permissions.AccessContentsInformation,'getSubObjectId')
def getSubObjectId(self, xml):
"""
Return the id of the subobject in an xupdate modification
......@@ -520,6 +550,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return object_id
return object_id
security.declareProtected(Permissions.AccessContentsInformation,'getHistoryIdFromSelect')
def getHistoryIdFromSelect(self, xml):
"""
Return the id of the subobject in an xupdate modification
......@@ -536,6 +567,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return object_id
return object_id
security.declareProtected(Permissions.AccessContentsInformation,'getSubObjectXml')
def getSubObjectXml(self, object_id, xml):
"""
Return the xml of the subobject which as the id object_id
......@@ -559,6 +591,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
# return self.convertXmlValue(data)
# return None
security.declareProtected(Permissions.AccessContentsInformation,'getAttribute')
def getAttribute(self, xml, param):
"""
Retrieve the given parameter from the xml
......@@ -569,6 +602,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return self.convertXmlValue(data,data_type='string')
return None
security.declareProtected(Permissions.AccessContentsInformation,'getObjectDocid')
def getObjectDocid(self, xml):
"""
Retrieve the docid
......@@ -579,6 +613,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return self.convertXmlValue(data)
return None
security.declareProtected(Permissions.AccessContentsInformation,'getObjectProperty')
def getObjectProperty(self, property, xml, data_type=None):
"""
Retrieve the given property
......@@ -598,6 +633,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return data
return None
security.declareProtected(Permissions.AccessContentsInformation,'convertToXml')
def convertToXml(self,xml):
"""
if xml is a string, convert it to a node
......@@ -610,6 +646,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
xml = self.getElementNodeList(xml)[0]
return xml
security.declareProtected(Permissions.AccessContentsInformation,'getObjectType')
def getObjectType(self, xml):
"""
Retrieve the portal type from an xml
......@@ -622,6 +659,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return portal_type
return portal_type
security.declareProtected(Permissions.AccessContentsInformation,'getPropertyType')
def getPropertyType(self, xml):
"""
Retrieve the portal type from an xml
......@@ -634,6 +672,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return p_type
return p_type
security.declareProtected(Permissions.AccessContentsInformation,'getXupdateObjectType')
def getXupdateObjectType(self, xml):
"""
Retrieve the portal type from an xupdate
......@@ -653,6 +692,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return None
security.declareProtected(Permissions.ModifyPortalContent, 'newObject')
def newObject(self, object=None, xml=None):
"""
modify the object with datas from
......@@ -662,6 +702,8 @@ class ERP5Conduit(XMLSyncUtilsMixin):
# Retrieve the list of users with a role and delete default roles
user_role_list = map(lambda x:x[0],object.get_local_roles())
object.manage_delLocalRoles(user_role_list)
if hasattr(object,'workflow_history'):
object.workflow_history = {}
if xml.nodeName.find('xupdate')>= 0:
xml = self.getElementNodeList(xml)[0]
for subnode in self.getElementNodeList(xml):
......@@ -680,7 +722,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
if args.has_key(keyword):
args[keyword] = self.convertXmlValue(args[keyword],keyword_type)
elif subnode.nodeName in self.ADDABLE_PROPERTY:
self.addNode(object=object,xml=subnode)
self.addNode(object=object,xml=subnode, force=1)
# We should first edit the object
args = self.getFormatedArgs(args=args)
LOG('newObject',0,"object.getpath: %s" % str(object.getPath()))
......@@ -691,9 +733,10 @@ class ERP5Conduit(XMLSyncUtilsMixin):
# Then we may create subobject
for subnode in self.getElementNodeList(xml):
if subnode.nodeName in (self.xml_object_tag,self.history_tag):
if subnode.nodeName in (self.xml_object_tag,): #,self.history_tag):
self.addNode(object=object,xml=subnode)
security.declareProtected(Permissions.AccessContentsInformation,'getStatusFromXml')
def getStatusFromXml(self, xml):
"""
Return a worklow status from xml
......@@ -705,6 +748,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
status[keyword] = value
return status
security.declareProtected(Permissions.AccessContentsInformation,'getXupdateElementList')
def getXupdateElementList(self, xml):
"""
Retrieve the list of xupdate:element subnodes
......@@ -716,6 +760,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
LOG('getXupdateElementList, e_list:%',0,e_list)
return e_list
security.declareProtected(Permissions.AccessContentsInformation,'getElementFromXupdate')
def getElementFromXupdate(self, xml):
"""
from a xupdate:element returns the element as xml
......@@ -740,6 +785,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return self.convertToXml(result)
return xml
security.declareProtected(Permissions.AccessContentsInformation,'getWorkflowActionFromXml')
def getWorkflowActionFromXml(self, xml):
"""
Return the list of workflow actions
......@@ -753,6 +799,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
action_list += [subnode]
return action_list
security.declareProtected(Permissions.AccessContentsInformation,'convertXmlValue')
def convertXmlValue(self, data, data_type=None):
"""
It is possible that the xml change the value, for example
......@@ -798,6 +845,7 @@ class ERP5Conduit(XMLSyncUtilsMixin):
# XXX is it the right place ? It should be in XupdateUtils, but here we
# have some specific things to do
security.declareProtected(Permissions.ModifyPortalContent, 'applyXupdate')
def applyXupdate(self, object=None, xupdate=None, conduit=None, force=0, **kw):
"""
Parse the xupdate and then it will call the conduit
......@@ -823,3 +871,25 @@ class ERP5Conduit(XMLSyncUtilsMixin):
return conflict_list
def isWorkflowActionAddable(self, object=None,status=None,wf_tool=None,xml=None):
"""
Some checking in order to check if we should add the workfow or not
"""
conflict_list = []
action_name = status['action']
authorized = 0
authorized_actions = wf_tool.getActionsFor(object)
LOG('isWorkflowActionAddable, status:',0,status)
LOG('isWorkflowActionAddable, authorized_actions:',0,authorized_actions)
for action in authorized_actions:
if action['id']==action_name:
authorized = 1
if not authorized:
string_io = StringIO()
PrettyPrint(xml,stream=string_io)
conflict = Conflict(object_path=object.getPhysicalPath(),
keyword=self.history_tag)
conflict.setXupdate(string_io.getvalue())
conflict.setRemoteValue(status)
conflict_list += [conflict]
return conflict_list
......@@ -29,11 +29,13 @@
from Globals import PersistentMapping
from time import gmtime,strftime # for anchors
from SyncCode import SyncCode
from Products.CMFCore.utils import getToolByName
from Acquisition import Implicit, aq_base
from zLOG import LOG
import md5
class Conflict(SyncCode):
class Conflict(SyncCode, Implicit):
"""
object_path : the path of the obect
keyword : an identifier of the conflict
......@@ -118,6 +120,21 @@ class Conflict(SyncCode):
except TypeError: # It happens when we try to store StringIO
self.remote_value = None
def applyLocalValue(self):
"""
after a conflict resolution, we have decided
to keep the local version of this object
"""
p_sync = getToolByName(self,'portal_synchronizations')
p_sync.applyLocalValue(self)
def applyRemoteValue(self):
"""
get the domain
"""
p_sync = getToolByName(self,'portal_synchronizations')
p_sync.applyRemoteValue(self)
def setDomain(self, domain):
"""
set the domain
......@@ -148,18 +165,6 @@ class Conflict(SyncCode):
"""
self.domain_id = domain_id
def applyRemoteValue():
"""
We will take the remote value for this conflict
"""
pass
def applyLocalValue():
"""
We will take the local value for this conflict
"""
pass
class Signature(SyncCode):
"""
status -- SENT, CONFLICT...
......@@ -179,7 +184,6 @@ class Signature(SyncCode):
self.partial_xml = None
self.action = None
self.setTempXML(None)
self.setTempXML(None)
self.resetConflictList()
self.md5_string = None
self.force = 0
......@@ -351,23 +355,39 @@ class Signature(SyncCode):
Return the actual action for a partial synchronization
"""
LOG('setConflictList, list',0,conflict_list)
if conflict_list is None:
if conflict_list is None or conflict_list==[]:
self.resetConflictList()
else:
new_conflict_list = []
#new_conflict_list = []
# If two conflicts are on the same objects, then
# we join them, so we have a conflict with many xupdate
for conflict in conflict_list:
found = None
for n_conflict in new_conflict_list:
if n_conflict.getObjectPath() == conflict.getObjectPath():
found = n_conflict
LOG('setConflictList, found',0,found)
if found == None:
new_conflict_list += [conflict]
else:
n_conflict.setXupdate(conflict.getXupdateList())
self.conflict_list = new_conflict_list
# for conflict in conflict_list:
# found = None
# for n_conflict in new_conflict_list:
# if n_conflict.getObjectPath() == conflict.getObjectPath():
# found = n_conflict
# LOG('setConflictList, found',0,found)
# if found == None:
# new_conflict_list += [conflict]
# else:
# n_conflict.setXupdate(conflict.getXupdateList())
#self.conflict_list = new_conflict_list
self.conflict_list = conflict_list
def delConflict(self, conflict):
"""
Return the actual action for a partial synchronization
"""
LOG('delConflict, conflict',0,conflict)
conflict_list = []
for c in self.getConflictList():
LOG('delConflict, c==conflict',0,c==aq_base(conflict))
if c != aq_base(conflict):
conflict_list += [c]
if conflict_list != []:
self.setConflictList(conflict_list)
else:
self.resetConflictList()
class Subscription(SyncCode):
"""
......
......@@ -70,7 +70,8 @@ class SyncCode(Persistent):
#ENCODING='iso-8859-1'
NOT_EDITABLE_PROPERTY = ('id','object','uid','xupdate:element','workflow_history',
action_tag = 'workflow_action'
NOT_EDITABLE_PROPERTY = ('id','object','uid','xupdate:element',action_tag,
'xupdate:attribute','local_role')
XUPDATE_INSERT = ('xupdate:insert-after','xupdate:insert-before')
XUPDATE_ADD = ('xupdate:append',)
......@@ -87,12 +88,13 @@ class SyncCode(Persistent):
dict_type_list = ('dict',)
pickle_type_list = ('pickle',)
xml_object_tag = 'object'
history_tag = 'workflow_history'
#history_tag = 'workflow_history'
history_tag = 'workflow_action'
local_role_tag = 'local_role'
local_role_list = (local_role_tag,'/'+local_role_tag)
action_tag = 'workflow_action'
ADDABLE_PROPERTY = (local_role_tag,history_tag)
sub_object_exp = "/object\[@id='.*'\]/object\[@id='.*'\]"
object_exp = "/object\[@id='.*'\]"
sub_sub_object_exp = "/object\[@id='.*'\]/object\[@id='.*'\]/object\[@id='.*'\]"
history_exp = "/object\[@id='.*'\]/%s\[@id='.*'\]" % history_tag
history_exp = "/%s\[@id='.*'\]" % history_tag
bad_history_exp = "/%s\[@id='.*'\]/" % history_tag
......@@ -101,7 +101,7 @@ Details
and the realm of attributes we synchronise (ie. the mapping)
2 The slave sends the "initialisation package" (SyncML specification), in this package,
2 The slave sends the "initialisation package" (SyncML specification), in this package,
appears several elements :
- the SynchHdr element with several informations about the protocol used. We will
......@@ -128,7 +128,7 @@ Details
4 The master sends the "Initialisation package" to the client with :
- the SynchHdr element with several informations about the protocol used,
- the SynchHdr element with several informations about the protocol used,
and also authentification informations.
- the Status element, in order to respond to the alert command sent by the client.
......@@ -141,12 +141,8 @@ Details
for us it will be 201 wich specifies a client-initiated, two-way slow-synchronisation.
This just means that both the server and the client sends all their data. Each
data must specify the DTD used.
//4 The master updates is database with the objects from the client. Then
//the master generate an xml file of differences between the two databases (tricky if
//at the beginning the client don't have any data), differences only all objects
//corresponding to the query.
At this step, we may eventually record the subscriber in the publication
of the master database for... (OPTION). This is like
the subscribtion is becoming member of mail list to be informed of
......@@ -190,7 +186,7 @@ Details
1 update the slave S(tn) <- S(tn) + DM(tn) + C(tn)
Detail implementation
blabla
......@@ -199,5 +195,5 @@ References
XMLDiff - http://www.garshol.priv.no/download/xmltools/prod/xmldiff.html
SyncML - http://www.syncml.org
ZSyncer - http://www.zope.org/Members/andym/ZSyncer
ZSyncer - http://www.zope.org/Members/andym/ZSyncer
......@@ -39,6 +39,7 @@ from Publication import Publication,Subscriber
from Subscription import Subscription,Signature
from xml.dom.ext.reader.Sax2 import FromXmlStream, FromXml
from XMLSyncUtils import *
from Products.ERP5Type import Permissions
from PublicationSynchronization import PublicationSynchronization
from SubscriptionSynchronization import SubscriptionSynchronization
#import sys
......@@ -140,6 +141,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
+ '?manage_tabs_message=Tool+updated.'
)
security.declareProtected(Permissions.ModifyPortalContent, 'addPublications')
def addPublications(self, id, publication_url, destination_path,
query, xml_mapping, RESPONSE=None):
"""
......@@ -153,9 +155,11 @@ class SynchronizationTool( UniqueObject, SimpleItem,
if RESPONSE is not None:
RESPONSE.redirect('managePublications')
security.declareProtected(Permissions.ModifyPortalContent, 'addSubscriptions')
def addSubscriptions(self, id, publication_url, subscription_url,
destination_path, query, xml_mapping, RESPONSE=None):
"""
XXX should be renamed as addSubscription
create a new subscription
"""
sub = Subscription(id, publication_url, subscription_url,
......@@ -166,6 +170,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
if RESPONSE is not None:
RESPONSE.redirect('manageSubscriptions')
security.declareProtected(Permissions.ModifyPortalContent, 'editPublications')
def editPublications(self, id, publication_url, destination_path,
query, xml_mapping, RESPONSE=None):
"""
......@@ -177,6 +182,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
if RESPONSE is not None:
RESPONSE.redirect('managePublications')
security.declareProtected(Permissions.ModifyPortalContent, 'editSubscriptions')
def editSubscriptions(self, id, publication_url, subscription_url,
destination_path, query, xml_mapping, RESPONSE=None):
"""
......@@ -188,6 +194,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
if RESPONSE is not None:
RESPONSE.redirect('manageSubscriptions')
security.declareProtected(Permissions.ModifyPortalContent, 'deletePublications')
def deletePublications(self, id, RESPONSE=None):
"""
delete a publication
......@@ -196,6 +203,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
if RESPONSE is not None:
RESPONSE.redirect('managePublications')
security.declareProtected(Permissions.ModifyPortalContent, 'deleteSubscriptions')
def deleteSubscriptions(self, id, RESPONSE=None):
"""
delete a subscription
......@@ -204,6 +212,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
if RESPONSE is not None:
RESPONSE.redirect('manageSubscriptions')
security.declareProtected(Permissions.ModifyPortalContent, 'ResetPublications')
def ResetPublications(self, id, RESPONSE=None):
"""
reset a publication
......@@ -212,15 +221,18 @@ class SynchronizationTool( UniqueObject, SimpleItem,
if RESPONSE is not None:
RESPONSE.redirect('managePublications')
security.declareProtected(Permissions.ModifyPortalContent, 'ResetSubscriptions')
def ResetSubscriptions(self, id, RESPONSE=None):
"""
reset a subscription
XXX R -> r
"""
self.list_subscriptions[id].resetAllSignatures()
self.list_subscriptions[id].resetAnchors()
if RESPONSE is not None:
RESPONSE.redirect('manageSubscriptions')
security.declareProtected(Permissions.AccessContentsInformation,'getPublicationList')
def getPublicationList(self):
"""
Return a list of publications
......@@ -233,6 +245,7 @@ class SynchronizationTool( UniqueObject, SimpleItem,
return_list += [self.list_publications[key]]
return return_list
security.declareProtected(Permissions.AccessContentsInformation,'getSubscriptionList')
def getSubscriptionList(self):
"""
Return a list of publications
......@@ -245,12 +258,17 @@ class SynchronizationTool( UniqueObject, SimpleItem,
return_list += [self.list_subscriptions[key]]
return return_list
security.declareProtected(Permissions.AccessContentsInformation,'getDomainList')
def getDomainList(self):
"""
Returns the list of subscriptions and publications
getSynchronizationList ? (mon choix)
getSubscriptionOrPublicationList ?
"""
return self.getSubscriptionList() + self.getPublicationList()
security.declareProtected(Permissions.AccessContentsInformation,'getConflictList')
def getConflictList(self, path=None):
"""
Retrieve the list of all conflicts
......@@ -260,27 +278,29 @@ class SynchronizationTool( UniqueObject, SimpleItem,
"""
conflict_list = []
for publication in self.getPublicationList():
pub_conflict_list = publication.getConflictList()
for conflict in pub_conflict_list:
#conflict.setDomain('Publication')
conflict.setDomain(publication)
conflict.setDomainId(publication.getId())
conflict_list += [conflict]
for subscriber in publication.getSubscriberList():
sub_conflict_list = subscriber.getConflictList()
for conflict in sub_conflict_list:
#conflict.setDomain('Publication')
conflict.setDomain(subscriber)
#conflict.setDomainId(subscriber.getId())
conflict_list += [conflict.__of__(self)]
for subscription in self.getSubscriptionList():
sub_conflict_list = subscription.getConflictList()
for conflict in sub_conflict_list:
#conflict.setDomain('Subscription')
conflict.setDomain(subscription)
conflict.setDomainId(subscription.getId())
conflict_list += [conflict]
#conflict.setDomainId(subscription.getId())
conflict_list += [conflict.__of__(self)]
if path is not None: # Retrieve only conflicts for a given path
new_list = []
for conflict in conflict_list:
if conflict.getObjectPath() == path:
new_list += [conflict]
new_list += [conflict.__of__(self)]
return new_list
return conflict_list
security.declareProtected(Permissions.AccessContentsInformation,'getSynchronizationState')
def getSynchronizationState(self, path):
"""
context : the context on which we are looking for state
......@@ -290,6 +310,11 @@ class SynchronizationTool( UniqueObject, SimpleItem,
then we have to check on a publication/subscription.
This method returns a mapping between subscription and states
JPS suggestion:
path -> object, document, context, etc.
type -> '/titi/toto' or ('','titi', 'toto') or <Base instance 1562567>
object = self.resolveContext(context) (method to add)
"""
conflict_list = self.getConflictList()
state_list= []
......@@ -324,71 +349,95 @@ class SynchronizationTool( UniqueObject, SimpleItem,
state_list += [[subscriber,state]]
return state_list
def manageLocalValue(self, domain, domain_id, object_path, RESPONSE=None):
security.declareProtected(Permissions.ModifyPortalContent, 'applyLocalValue')
def applyLocalValue(self, conflict):
"""
after a conflict resolution, we have decided
to keep the local version of an object
XXXC Local ? Remote ?
applyPublisherValue ? (JPS 1)
applySubscriberValue ?
applyPublicationValue ? (JPS 2)
applySubscriptionValue ?
applyPublishedValue ? (JPS 3)
applySubscribedValue ?
"""
object = self.unrestrictedTraverse(conflict.getObjectPath())
subscriber = conflict.getDomain()
# get the signature:
LOG('p_sync.setLocalObject, subscriber: ',0,subscriber)
signature = subscriber.getSignature(object.getId()) # XXX may be change for rid
signature.delConflict(conflict)
if signature.getConflictList() == []:
signature.setStatus(self.PUB_CONFLICT_MERGE)
security.declareProtected(Permissions.ModifyPortalContent, 'applyRemoteValue')
def applyRemoteValue(self, conflict):
"""
after a conflict resolution, we have decided
to keep the local version of an object
"""
object = self.unrestrictedTraverse(conflict.getObjectPath())
subscriber = conflict.getDomain()
# get the signature:
LOG('p_sync.setRemoteObject, subscriber: ',0,subscriber)
signature = subscriber.getSignature(object.getId()) # XXX may be change for rid
conduit = ERP5Conduit()
for xupdate in conflict.getXupdateList():
conduit.updateNode(xml=xupdate,object=object,force=1)
signature.delConflict(conflict)
if signature.getConflictList() == []:
signature.setStatus(self.PUB_CONFLICT_MERGE)
security.declareProtected(Permissions.ModifyPortalContent, 'manageLocalValue')
def manageLocalValue(self, subscription_url, keyword, object_path, RESPONSE=None):
"""
Do whatever needed in order to store the local value on
the remote server
Suggestion:
manage_applyLocalValue XXX
Suggestion:
add global apply (not conflict per conflict) XXX
Suggestion (API)
add method to view document with applied xupdate
of a given subscriber XX (ex. viewSubscriberDocument?path=ddd&subscriber_id=dddd)
Version=Version CPS
"""
# Retrieve the conflict object
conflict=None
if type(object_path) is type(''):
object_path = tuple(object_path.split('/'))
for item in self.getConflictList():
if item.getDomain() == domain and item.getDomainId()==domain_id \
and item.getObjectPath()==object_path:
conflict=item
break
publication = subscriber = None
if conflict.getDomain()=='Publication': # may be we do not need the case 'subscription'
for publication_item in self.getPublicationList():
if conflict in publication_item.getConflictList():
publication = publication_item
for subscriber_item in publication.getSubscriberList():
if conflict in subscriber_item.getConflictList():
subscriber = subscriber_item
if subscriber is not None and publication is not None:
# Retrieve the signature and change the status
publication_path = tuple(publication.getDestinationPath().split('/'))
# Get 167 in /nexedi/server/167/default_message
signature_id = object_path[len(publication_path)]
signature = subscriber.getSignature(signature_id)
signature.setStatus(signature.PUB_CONFLICT_MERGE)
# Then launch the synchronization (wich will only be upate for conflict
self.PubSync(publication.getId(),subscriber=subscriber)
LOG('manageLocalValue',0,'%s %s %s' % (str(subscription_url),
str(keyword),
str(object_path)))
for conflict in self.getConflictList():
LOG('manageLocalValue, conflict:',0,conflict)
if conflict.getKeyword() == keyword:
LOG('manageLocalValue',0,'found the keyword')
if '/'.join(conflict.getObjectPath())==object_path:
if conflict.getDomain().getSubscriptionUrl()==subscription_url:
conflict.applyLocalValue()
if RESPONSE is not None:
RESPONSE.redirect('manageConflicts')
def manageRemoteValue(self, domain, domain_id, object_path, RESPONSE=None):
security.declareProtected(Permissions.ModifyPortalContent, 'manageRemoteValue')
def manageRemoteValue(self, subscription_url, keyword, object_path, RESPONSE=None):
"""
Do whatever needed in order to store the remote value locally
and confirmed that the remote box should keep it's value
"""
conflict=None
if type(object_path) is type(''):
object_path = tuple(object_path.split('/'))
for item in self.getConflictList():
if item.getDomain() == domain and item.getDomainId()==domain_id \
and item.getObjectPath()==object_path:
conflict=item
break
publication = subscriber = None
if conflict.getDomain()=='Publication': # may be we do not need the case 'subscription'
for publication_item in self.getPublicationList():
if conflict in publication_item.getConflictList():
publication = publication_item
for subscriber_item in publication.getSubscriberList():
if conflict in subscriber_item.getConflictList():
subscriber = subscriber_item
if subscriber is not None and publication is not None:
# Retrieve the signature and change the status
publication_path = tuple(publication.getDestinationPath().split('/'))
# Get 167 in /nexedi/server/167/default_message
signature_id = object_path[len(publication_path)]
signature = subscriber.getSignature(signature_id)
signature.setStatus(signature.PUB_CONFLICT_CLIENT_WIN)
# Then launch the synchronization (wich will only be upate for conflict
self.PubSync(publication.getId(),subscriber=subscriber)
LOG('manageLocalValue',0,'%s %s %s' % (str(subscription_url),
str(keyword),
str(object_path)))
for conflict in self.getConflictList():
LOG('manageLocalValue, conflict:',0,conflict)
if conflict.getKeyword() == keyword:
LOG('manageLocalValue',0,'found the keyword')
if '/'.join(conflict.getObjectPath())==object_path:
if conflict.getDomain().getSubscriptionUrl()==subscription_url:
conflict.applyRemoteValue()
if RESPONSE is not None:
RESPONSE.redirect('manageConflicts')
......
......@@ -73,23 +73,27 @@ Conflict management with n clients, n >= 2
I guess the best way is to just solve conflict one by one, then we are still
free to make another method wich solve for all versions by the same time.
- So we have to do :
Conflict2.manageRemoteObject()
Conflict1.manageLocalObject() # wich is the version of D because of the previous call
Conflict3.manageLocalObject()
Conflict4.manageLocalObject()
Conflict2.setRemoteObject()
Conflict1.setLocalObject() # wich is the version of D because of the previous call
Conflict3.setLocalObject()
Conflict4.setLocalObject()
- May be we can do a global method, like :
Conflict2.manageGlobalRemoteObject() wich implicitly call
Conflict1.manageLocalObject()
and Conflict3.manageGlobalLocalObject() wich implicitly call
Conflict4.manageLocalObject()
- Conflict2.manageRemoteObject() have to apply all xupdate strings stored
in Conflict2
- Conflict3.manageLocalObject() have to set the status as SYNCHRONIZED and then
it has to delete itself (Conflict3) -> How ??
Probably the best way is to call the synchronizationTool wich know everything
Conflict2.setGlobalRemoteObject() wich implicitly call
Conflict1.setLocalObject()
and Conflict3.setGlobalLocalObject() wich implicitly call
Conflict4.setLocalObject()
- Conflict2.setRemoteObject() have to apply all xupdate strings stored
in Conflict2. Then it have to set the status as CONFLICT_CLIENT_WIN.
- Conflict3.setLocalObject() have to set the status as CONFLICT_MERGE. How ??
- Probably the best way is to call the synchronizationTool wich know everything
about subscription and subscriber.
- synchronizationtool.setLocalObject should have as parameter: the conflict (wich
store the subscriber), that's all
- then we can look at the signature of the object, delete the corresponding
Conflict, and if there is no conflict left, then we can set the signature
as CONFLICT_MERGE
- At this state, we do have the /w/x/truc of D, and the /w/x/machin of B, and
there is no conflict left, at least on the server side.
- at the time of the next synchronization, the server should send is new version
#- at the time of the next synchronization, the server should send is new version
of /w/x/truc and /w/x/machin to B, C and D, so that everyone is synchronized
without conflict.
\ No newline at end of file
without conflict.
......@@ -40,11 +40,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
<th align="left" valign="top">Local Value</th>
<th align="left" valign="top">Remote Value</th>
</tr>
<dtml-in getConflictList>
<dtml-in prefix="loop" expr="getConflictList()">
<tr>
<td align="left" valign="top"><a href="manageLocalValue?domain=<dtml-var domain>&domain_id=<dtml-var domain_id>&object_path=<dtml-var "'/'.join(object_path)">">Local</a> <a href="manageRemoteValue?domain=<dtml-var domain>&domain_id=<dtml-var domain_id>&object_path=<dtml-var "'/'.join(object_path)">">Remote</a></td>
<td align="left" valign="top"><a href="manageLocalValue?subscription_url=<dtml-var expr="loop_item.getDomain().getSubscriptionUrl()">&keyword=<dtml-var keyword>&object_path=<dtml-var "'/'.join(object_path)">">Local</a> <a href="manageRemoteValue?subscription_url=<dtml-var expr="loop_item.getDomain().getSubscriptionUrl()">&keyword=<dtml-var keyword>&object_path=<dtml-var "'/'.join(object_path)">">Remote</a></td>
<td align="left" valign="top"><dtml-var expr="loop_item.getDomain().getSubscriptionUrl()"></td>
<td align="left" valign="top"><dtml-var domain></td>
<td align="left" valign="top"><dtml-var domain_id></td>
<td align="left" valign="top"><dtml-var "'/'.join(object_path)"></td>
<td align="left" valign="top"><dtml-var keyword></td>
<td align="left" valign="top"><dtml-var local_value></td>
......
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