comparison src/memory/params.py @ 1477:ac522c4dab0e

core (params): jids_list type management
author Goffi <goffi@goffi.org>
date Thu, 20 Aug 2015 18:35:27 +0200
parents c7fd121a6180
children 8d61160ee4b8
comparison
equal deleted inserted replaced
1476:48706f4ff19c 1477:ac522c4dab0e
25 from xml.dom import minidom, NotFoundErr 25 from xml.dom import minidom, NotFoundErr
26 from sat.core.log import getLogger 26 from sat.core.log import getLogger
27 log = getLogger(__name__) 27 log = getLogger(__name__)
28 from twisted.internet import defer 28 from twisted.internet import defer
29 from twisted.python.failure import Failure 29 from twisted.python.failure import Failure
30 from twisted.words.protocols.jabber import jid
30 from sat.tools.xml_tools import paramsXML2XMLUI 31 from sat.tools.xml_tools import paramsXML2XMLUI
32
33 # TODO: params should be rewritten using Twisted directly instead of minidom
34 # general params should be linked to sat.conf and kept synchronised
35 # this need an overall simplification to make maintenance easier
31 36
32 37
33 class Params(object): 38 class Params(object):
34 """This class manage parameters with xml""" 39 """This class manage parameters with xml"""
35 ### TODO: add desciption in params 40 ### TODO: add desciption in params
75 'force_server_param': C.FORCE_SERVER_PARAM, 80 'force_server_param': C.FORCE_SERVER_PARAM,
76 'force_port_param': C.FORCE_PORT_PARAM, 81 'force_port_param': C.FORCE_PORT_PARAM,
77 'new_account_label': D_("Register new account"), 82 'new_account_label': D_("Register new account"),
78 'autoconnect_label': D_('Connect on frontend startup'), 83 'autoconnect_label': D_('Connect on frontend startup'),
79 'autodisconnect_label': D_('Disconnect on frontend closure'), 84 'autodisconnect_label': D_('Disconnect on frontend closure'),
80 'category_misc': D_("Misc"),
81 } 85 }
82 86
83 def load_default_params(self): 87 def load_default_params(self):
84 self.dom = minidom.parseString(Params.default_xml.encode('utf-8')) 88 self.dom = minidom.parseString(Params.default_xml.encode('utf-8'))
85 89
111 default_dom = minidom.parseString(Params.default_xml.encode('utf-8')) 115 default_dom = minidom.parseString(Params.default_xml.encode('utf-8'))
112 self._mergeParams(default_dom.documentElement, self.dom.documentElement) 116 self._mergeParams(default_dom.documentElement, self.dom.documentElement)
113 117
114 def loadGenParams(self): 118 def loadGenParams(self):
115 """Load general parameters data from storage 119 """Load general parameters data from storage
116 @return: deferred triggered once params are loaded""" 120
121 @return: deferred triggered once params are loaded
122 """
117 return self.storage.loadGenParams(self.params_gen) 123 return self.storage.loadGenParams(self.params_gen)
118 124
119 def loadIndParams(self, profile, cache=None): 125 def loadIndParams(self, profile, cache=None):
120 """Load individual parameters 126 """Load individual parameters
127
121 set self.params cache or a temporary cache 128 set self.params cache or a temporary cache
122 @param profile: profile to load (*must exist*) 129 @param profile: profile to load (*must exist*)
123 @param cache: if not None, will be used to store the value, as a short time cache 130 @param cache: if not None, will be used to store the value, as a short time cache
124 @return: deferred triggered once params are loaded""" 131 @return: deferred triggered once params are loaded
132 """
125 if cache is None: 133 if cache is None:
126 self.params[profile] = {} 134 self.params[profile] = {}
127 return self.storage.loadIndParams(self.params[profile] if cache is None else cache, profile) 135 return self.storage.loadIndParams(self.params[profile] if cache is None else cache, profile)
128 136
129 def purgeProfile(self, profile): 137 def purgeProfile(self, profile):
130 """Remove cache data of a profile 138 """Remove cache data of a profile
131 @param profile: %(doc_profile)s""" 139
140 @param profile: %(doc_profile)s
141 """
132 try: 142 try:
133 del self.params[profile] 143 del self.params[profile]
134 except KeyError: 144 except KeyError:
135 log.error(_(u"Trying to purge cache of a profile not in memory: [%s]") % profile) 145 log.error(_(u"Trying to purge cache of a profile not in memory: [%s]") % profile)
136 146
147 self.params = {} 157 self.params = {}
148 self.params_gen = {} 158 self.params_gen = {}
149 159
150 def asyncCreateProfile(self, profile): 160 def asyncCreateProfile(self, profile):
151 """Create a new profile 161 """Create a new profile
162
152 @param profile: name of the profile 163 @param profile: name of the profile
153 @param callback: called when the profile actually exists in database and memory 164 @param callback: called when the profile actually exists in database and memory
154 @return: a Deferred instance 165 @return: a Deferred instance
155 """ 166 """
156 if self.storage.hasProfile(profile): 167 if self.storage.hasProfile(profile):
208 raise exceptions.ProfileUnknownError 219 raise exceptions.ProfileUnknownError
209 return profile_key 220 return profile_key
210 221
211 def __get_unique_node(self, parent, tag, name): 222 def __get_unique_node(self, parent, tag, name):
212 """return node with given tag 223 """return node with given tag
224
213 @param parent: parent of nodes to check (e.g. documentElement) 225 @param parent: parent of nodes to check (e.g. documentElement)
214 @param tag: tag to check (e.g. "category") 226 @param tag: tag to check (e.g. "category")
215 @param name: name to check (e.g. "JID") 227 @param name: name to check (e.g. "JID")
216 @return: node if it exist or None 228 @return: node if it exist or None
217 """ 229 """
222 #the node is new 234 #the node is new
223 return None 235 return None
224 236
225 def updateParams(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=''): 237 def updateParams(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=''):
226 """import xml in parameters, update if the param already exists 238 """import xml in parameters, update if the param already exists
239
227 If security_limit is specified and greater than -1, the parameters 240 If security_limit is specified and greater than -1, the parameters
228 that have a security level greater than security_limit are skipped. 241 that have a security level greater than security_limit are skipped.
229 @param xml: parameters in xml form 242 @param xml: parameters in xml form
230 @param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure 243 @param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure
231 @param app: name of the frontend registering the parameters or empty value 244 @param app: name of the frontend registering the parameters or empty value
232 """ 245 """
246 # TODO: should word with domish.Element
233 src_parent = minidom.parseString(xml.encode('utf-8')).documentElement 247 src_parent = minidom.parseString(xml.encode('utf-8')).documentElement
234 248
235 def pre_process_app_node(src_parent, security_limit, app): 249 def pre_process_app_node(src_parent, security_limit, app):
236 """Parameters that are registered from a frontend must be checked""" 250 """Parameters that are registered from a frontend must be checked"""
237 to_remove = [] 251 to_remove = []
276 pre_process_app_node(src_parent, security_limit, app) 290 pre_process_app_node(src_parent, security_limit, app)
277 import_node(self.dom.documentElement, src_parent) 291 import_node(self.dom.documentElement, src_parent)
278 292
279 def paramsRegisterApp(self, xml, security_limit, app): 293 def paramsRegisterApp(self, xml, security_limit, app):
280 """Register frontend's specific parameters 294 """Register frontend's specific parameters
295
281 If security_limit is specified and greater than -1, the parameters 296 If security_limit is specified and greater than -1, the parameters
282 that have a security level greater than security_limit are skipped. 297 that have a security level greater than security_limit are skipped.
283 @param xml: XML definition of the parameters to be added 298 @param xml: XML definition of the parameters to be added
284 @param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure 299 @param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure
285 @param app: name of the frontend registering the parameters 300 @param app: name of the frontend registering the parameters
303 def __default_ko(self, failure, name, category): 318 def __default_ko(self, failure, name, category):
304 log.error(_(u"Can't determine default value for [%(category)s/%(name)s]: %(reason)s") % {'category': category, 'name': name, 'reason': str(failure.value)}) 319 log.error(_(u"Can't determine default value for [%(category)s/%(name)s]: %(reason)s") % {'category': category, 'name': name, 'reason': str(failure.value)})
305 320
306 def setDefault(self, name, category, callback, errback=None): 321 def setDefault(self, name, category, callback, errback=None):
307 """Set default value of parameter 322 """Set default value of parameter
323
308 'default_cb' attibute of parameter must be set to 'yes' 324 'default_cb' attibute of parameter must be set to 'yes'
309 @param name: name of the parameter 325 @param name: name of the parameter
310 @param category: category of the parameter 326 @param category: category of the parameter
311 @param callback: must return a string with the value (use deferred if needed) 327 @param callback: must return a string with the value (use deferred if needed)
312 @param errback: must manage the error with args failure, name, category 328 @param errback: must manage the error with args failure, name, category
339 @return: str 355 @return: str
340 """ 356 """
341 if attr == 'value': 357 if attr == 'value':
342 value_to_use = value if value is not None else node.getAttribute(attr) # we use value (user defined) if it exist, else we use node's default value 358 value_to_use = value if value is not None else node.getAttribute(attr) # we use value (user defined) if it exist, else we use node's default value
343 if node.getAttribute('type') == 'bool': 359 if node.getAttribute('type') == 'bool':
344 return value_to_use.lower() not in ('false', '0', 'no') 360 return C.bool(value_to_use)
345 if node.getAttribute('type') == 'int': 361 if node.getAttribute('type') == 'int':
346 return int(value_to_use) 362 return int(value_to_use)
347 elif node.getAttribute('type') == 'list': 363 elif node.getAttribute('type') == 'list':
348 options = [option for option in node.childNodes if option.nodeName == 'option'] 364 options = [option for option in node.childNodes if option.nodeName == 'option']
349 values = [option.getAttribute('value') for option in options] 365 values = [option.getAttribute('value') for option in options]
358 if len(selected) == 0: 374 if len(selected) == 0:
359 log.error(_(u'Parameter (%(cat)s, %(param)s) of type list has no default option!') % {'cat': cat, 'param': param}) 375 log.error(_(u'Parameter (%(cat)s, %(param)s) of type list has no default option!') % {'cat': cat, 'param': param})
360 else: 376 else:
361 log.error(_(u'Parameter (%(cat)s, %(param)s) of type list has more than one default option!') % {'cat': cat, 'param': param}) 377 log.error(_(u'Parameter (%(cat)s, %(param)s) of type list has more than one default option!') % {'cat': cat, 'param': param})
362 raise exceptions.DataError 378 raise exceptions.DataError
379 elif node.getAttribute('type') == 'jids_list':
380 jids = value.split('\t')
381 to_delete = []
382 for idx, value in enumerate(jids):
383 try:
384 jids[idx] = jid.JID(value)
385 except jid.InvalidFormat:
386 log.warning(u"Incorrect jid value found in jids list: [{}]".format(value))
387 to_delete.append(value)
388 for value in to_delete:
389 jids.remove(value)
390 return jids
363 return value_to_use 391 return value_to_use
364 return node.getAttribute(attr) 392 return node.getAttribute(attr)
365 393
366 def _getAttr(self, node, attr, value): 394 def _getAttr(self, node, attr, value):
367 """Get attribute value (synchronous). 395 """Get attribute value (synchronous).
368 396
369 /!\ This method can not be used to retrieve password values. 397 /!\ This method can not be used to retrieve password values.
370
371 @param node: XML param node 398 @param node: XML param node
372 @param attr: name of the attribute to get (e.g.: 'value' or 'type') 399 @param attr: name of the attribute to get (e.g.: 'value' or 'type')
373 @param value: user defined value 400 @param value: user defined value
374 @return: str 401 @return (unicode, bool, int, list): value to retrieve
375 """ 402 """
376 if attr == 'value' and node.getAttribute('type') == 'password': 403 if attr == 'value' and node.getAttribute('type') == 'password':
377 raise exceptions.InternalError('To retrieve password values, use _asyncGetAttr instead of _getAttr') 404 raise exceptions.InternalError('To retrieve password values, use _asyncGetAttr instead of _getAttr')
378 return self._getAttr_internal(node, attr, value) 405 return self._getAttr_internal(node, attr, value)
379 406
380 def _asyncGetAttr(self, node, attr, value, profile=None): 407 def _asyncGetAttr(self, node, attr, value, profile=None):
381 """Get attribute value. 408 """Get attribute value.
382 409
383 Profile passwords are returned hashed (if not empty), 410 Profile passwords are returned hashed (if not empty),
384 other passwords are returned decrypted (if not empty). 411 other passwords are returned decrypted (if not empty).
385
386 @param node: XML param node 412 @param node: XML param node
387 @param attr: name of the attribute to get (e.g.: 'value' or 'type') 413 @param attr: name of the attribute to get (e.g.: 'value' or 'type')
388 @param value: user defined value 414 @param value: user defined value
389 @param profile: %(doc_profile)s 415 @param profile: %(doc_profile)s
390 @return: a deferred str 416 @return (unicode, bool, int, list): Deferred value to retrieve
391 """ 417 """
392 value = self._getAttr_internal(node, attr, value) 418 value = self._getAttr_internal(node, attr, value)
393 if attr != 'value' or node.getAttribute('type') != 'password': 419 if attr != 'value' or node.getAttribute('type') != 'password':
394 return defer.succeed(value) 420 return defer.succeed(value)
395 param_cat = node.parentNode.getAttribute('name') 421 param_cat = node.parentNode.getAttribute('name')
422 def getParamA(self, name, category, attr="value", profile_key=C.PROF_KEY_NONE): 448 def getParamA(self, name, category, attr="value", profile_key=C.PROF_KEY_NONE):
423 """Helper method to get a specific attribute. 449 """Helper method to get a specific attribute.
424 450
425 /!\ This method would return encrypted password values, 451 /!\ This method would return encrypted password values,
426 to get the plain values you have to use _asyncGetParamA. 452 to get the plain values you have to use _asyncGetParamA.
427 453 @param name: name of the parameter
428 @param name: name of the parameter 454 @param category: category of the parameter
429 @param category: category of the parameter 455 @param attr: name of the attribute (default: "value")
430 @param attr: name of the attribute (default: "value") 456 @param profile: owner of the param (@ALL@ for everyone)
431 @param profile: owner of the param (@ALL@ for everyone) 457 @return: attribute
432 @return: attribute 458 """
433 """
434 #FIXME: looks really dirty and buggy, need to be reviewed/refactored 459 #FIXME: looks really dirty and buggy, need to be reviewed/refactored
435 node = self._getParamNode(name, category) 460 node = self._getParamNode(name, category)
436 if not node: 461 if not node:
437 log.error(_(u"Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category}) 462 log.error(_(u"Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
438 raise exceptions.NotFound 463 raise exceptions.NotFound
464 d.addCallback(self.__type_to_string) 489 d.addCallback(self.__type_to_string)
465 return d 490 return d
466 491
467 def asyncGetParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): 492 def asyncGetParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE):
468 """Helper method to get a specific attribute. 493 """Helper method to get a specific attribute.
494
469 @param name: name of the parameter 495 @param name: name of the parameter
470 @param category: category of the parameter 496 @param category: category of the parameter
471 @param attr: name of the attribute (default: "value") 497 @param attr: name of the attribute (default: "value")
472 @param profile: owner of the param (@ALL@ for everyone) 498 @param profile: owner of the param (@ALL@ for everyone)
473 @return: Deferred 499 @return: Deferred
502 d = self.storage.getIndParam(category, name, profile) 528 d = self.storage.getIndParam(category, name, profile)
503 return d.addCallback(lambda value: self._asyncGetAttr(node[1], attr, value, profile)) 529 return d.addCallback(lambda value: self._asyncGetAttr(node[1], attr, value, profile))
504 530
505 def _getParam(self, category, name, type_=C.INDIVIDUAL, cache=None, profile=C.PROF_KEY_NONE): 531 def _getParam(self, category, name, type_=C.INDIVIDUAL, cache=None, profile=C.PROF_KEY_NONE):
506 """Return the param, or None if it doesn't exist 532 """Return the param, or None if it doesn't exist
533
507 @param category: param category 534 @param category: param category
508 @param name: param name 535 @param name: param name
509 @param type_: GENERAL or INDIVIDUAL 536 @param type_: GENERAL or INDIVIDUAL
510 @param cache: temporary cache, to use when profile is not logged 537 @param cache: temporary cache, to use when profile is not logged
511 @param profile: the profile name (not profile key, i.e. name and not something like @DEFAULT@) 538 @param profile: the profile name (not profile key, i.e. name and not something like @DEFAULT@)
513 """ 540 """
514 if type_ == C.GENERAL: 541 if type_ == C.GENERAL:
515 if (category, name) in self.params_gen: 542 if (category, name) in self.params_gen:
516 return self.params_gen[(category, name)] 543 return self.params_gen[(category, name)]
517 return None # This general param has the default value 544 return None # This general param has the default value
518 assert (type_ == C.INDIVIDUAL) 545 assert type_ == C.INDIVIDUAL
519 if profile == C.PROF_KEY_NONE: 546 if profile == C.PROF_KEY_NONE:
520 raise exceptions.ProfileNotSetError 547 raise exceptions.ProfileNotSetError
521 if profile in self.params: 548 if profile in self.params:
522 cache = self.params[profile] # if profile is in main cache, we use it, 549 cache = self.params[profile] # if profile is in main cache, we use it,
523 # ignoring the temporary cache 550 # ignoring the temporary cache
525 raise exceptions.ProfileNotInCacheError 552 raise exceptions.ProfileNotInCacheError
526 if (category, name) not in cache: 553 if (category, name) not in cache:
527 return None 554 return None
528 return cache[(category, name)] 555 return cache[(category, name)]
529 556
530 def __constructProfileXml(self, security_limit, app, profile): 557 def _constructProfileXml(self, security_limit, app, profile):
531 """Construct xml for asked profile, filling values when needed 558 """Construct xml for asked profile, filling values when needed
559
532 /!\ as noticed in doc, don't forget to unlink the minidom.Document 560 /!\ as noticed in doc, don't forget to unlink the minidom.Document
533 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params. 561 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
534 Otherwise sole the params which have a security level defined *and* 562 Otherwise sole the params which have a security level defined *and*
535 lower or equal to the specified value are returned. 563 lower or equal to the specified value are returned.
536 @param app: name of the frontend requesting the parameters, or '' to get all parameters 564 @param app: name of the frontend requesting the parameters, or '' to get all parameters
599 else: 627 else:
600 try: 628 try:
601 option.removeAttribute('selected') 629 option.removeAttribute('selected')
602 except NotFoundErr: 630 except NotFoundErr:
603 pass 631 pass
632 elif dest_params[name].getAttribute('type') == 'jids_list':
633 jids = profile_value.split('\t') # FIXME: it's not good to use tabs as separator !
634 for jid_ in jids:
635 jid_elt = prof_xml.createElement('jid')
636 jid_elt.appendChild(prof_xml.createTextNode(jid_))
637 dest_params[name].appendChild(jid_elt)
604 else: 638 else:
605 dest_params[name].setAttribute('value', profile_value) 639 dest_params[name].setAttribute('value', profile_value)
606 if new_node: 640 if new_node:
607 prof_xml.documentElement.appendChild(dest_cat) 641 prof_xml.documentElement.appendChild(dest_cat)
608 642
641 d = self.getParams(security_limit, app, profile) 675 d = self.getParams(security_limit, app, profile)
642 return d.addCallback(lambda param_xml: paramsXML2XMLUI(param_xml)) 676 return d.addCallback(lambda param_xml: paramsXML2XMLUI(param_xml))
643 677
644 def getParams(self, security_limit, app, profile_key): 678 def getParams(self, security_limit, app, profile_key):
645 """Construct xml for asked profile, take params xml as skeleton 679 """Construct xml for asked profile, take params xml as skeleton
680
646 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params. 681 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
647 Otherwise sole the params which have a security level defined *and* 682 Otherwise sole the params which have a security level defined *and*
648 lower or equal to the specified value are returned. 683 lower or equal to the specified value are returned.
649 @param app: name of the frontend requesting the parameters, or '' to get all parameters 684 @param app: name of the frontend requesting the parameters, or '' to get all parameters
650 @param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile. 685 @param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile.
658 def returnXML(prof_xml): 693 def returnXML(prof_xml):
659 return_xml = prof_xml.toxml() 694 return_xml = prof_xml.toxml()
660 prof_xml.unlink() 695 prof_xml.unlink()
661 return '\n'.join((line for line in return_xml.split('\n') if line)) 696 return '\n'.join((line for line in return_xml.split('\n') if line))
662 697
663 return self.__constructProfileXml(security_limit, app, profile).addCallback(returnXML) 698 return self._constructProfileXml(security_limit, app, profile).addCallback(returnXML)
664 699
665 def getParamsForCategory(self, category, security_limit, app, profile_key): 700 def getParamsForCategory(self, category, security_limit, app, profile_key):
666 """ 701 """
667 @param category: the desired category 702 @param category: the desired category
668 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params. 703 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
686 return result 721 return result
687 722
688 prof_xml.unlink() 723 prof_xml.unlink()
689 return "<category />" 724 return "<category />"
690 725
691 d = self.__constructProfileXml(security_limit, app, profile) 726 d = self._constructProfileXml(security_limit, app, profile)
692 return d.addCallback(returnCategoryXml) 727 return d.addCallback(returnCategoryXml)
693 728
694 def _getParamNode(self, name, category, type_="@ALL@"): # FIXME: is type_ useful ? 729 def _getParamNode(self, name, category, type_="@ALL@"): # FIXME: is type_ useful ?
695 """Return a node from the param_xml 730 """Return a node from the param_xml
696 @param name: name of the node 731 @param name: name of the node
781 if self.host.isConnected(profile): 816 if self.host.isConnected(profile):
782 self.host.bridge.paramUpdate(name, value, category, profile) 817 self.host.bridge.paramUpdate(name, value, category, profile)
783 self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile) 818 self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile)
784 return defer.succeed(None) 819 return defer.succeed(None)
785 820
786 assert (node[0] == C.INDIVIDUAL) 821 assert node[0] == C.INDIVIDUAL
787 assert (profile_key != C.PROF_KEY_NONE) 822 assert profile_key != C.PROF_KEY_NONE
788 823
789 d_list = [] 824 d_list = []
790 if type_ == "button": 825 if type_ == "button":
791 log.debug(u"Clicked param button %s" % node.toxml()) 826 log.debug(u"Clicked param button %s" % node.toxml())
792 return defer.succeed(None) 827 return defer.succeed(None)