comparison src/memory/memory.py @ 914:1a3ba959f0ab

core (memory): moved Params in its own module + introduced a new core/constants module, and moved some constants there
author Goffi <goffi@goffi.org>
date Fri, 21 Mar 2014 16:08:11 +0100
parents fab49a1d5ea2
children 1a759096ccbd
comparison
equal deleted inserted replaced
913:d08cbdd566e2 914:1a3ba959f0ab
15 # GNU Affero General Public License for more details. 15 # GNU Affero General Public License for more details.
16 16
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from __future__ import with_statement
21 from sat.core.i18n import _ 20 from sat.core.i18n import _
22 21
23 import os.path 22 import os.path
24 import csv 23 import csv
25 from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError 24 from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError
26 from xml.dom import minidom
27 from uuid import uuid4 25 from uuid import uuid4
28 from logging import debug, info, warning, error 26 from logging import debug, info, warning, error
29 from twisted.internet import defer, reactor 27 from twisted.internet import defer, reactor
30 from twisted.words.protocols.jabber import jid 28 from twisted.words.protocols.jabber import jid
31 from twisted.python.failure import Failure 29 from sat.core import exceptions
32 from sat.tools.xml_tools import paramsXML2XMLUI 30 from sat.core.constants import Const as C
33 from sat.core.default_config import default_config 31 from sat.core.default_config import default_config
34 from sat.memory.sqlite import SqliteStorage 32 from sat.memory.sqlite import SqliteStorage
35 from sat.memory.persistent import PersistentDict 33 from sat.memory.persistent import PersistentDict
36 from sat.core import exceptions 34 from sat.memory.params import Params
37
38 SAVEFILE_DATABASE = "/sat.db"
39 NO_SECURITY_LIMIT = -1
40 INDIVIDUAL = "individual"
41 GENERAL = "general"
42 35
43 36
44 class Sessions(object): 37 class Sessions(object):
45 DEFAULT_TIMEOUT = 600 38 DEFAULT_TIMEOUT = 600
46 39
108 def keys(self): 101 def keys(self):
109 return self._sessions.keys() 102 return self._sessions.keys()
110 103
111 def iterkeys(self): 104 def iterkeys(self):
112 return self._sessions.iterkeys() 105 return self._sessions.iterkeys()
113
114
115 class Params(object):
116 """This class manage parameters with xml"""
117 ### TODO: add desciption in params
118
119 #TODO: move Watched in a plugin
120 #TODO: when priority is changed, a new presence stanza must be emitted
121 #TODO: int type (Priority should be int instead of string)
122 default_xml = u"""
123 <params>
124 <general>
125 </general>
126 <individual>
127 <category name="Connection" label="%(category_connection)s">
128 <param name="JabberID" value="name@example.org/SàT" type="string" />
129 <param name="Password" value="" type="password" />
130 <param name="Priority" value="50" type="string" />
131 <param name="Server" value="example.org" type="string" />
132 <param name="Port" value="5222" type="string" />
133 <param name="NewAccount" label="%(label_NewAccount)s" type="button" callback_id="registerNewAccount"/>
134 <param name="autoconnect" label="%(label_autoconnect)s" value="true" type="bool" />
135 <param name="autodisconnect" label="%(label_autodisconnect)s" value="false" type="bool" />
136 </category>
137 <category name="Misc" label="%(category_misc)s">
138 <param name="Watched" value="test@Jabber.goffi.int" type="string" />
139 </category>
140 </individual>
141 </params>
142 """ % {
143 'category_connection': _("Connection"),
144 'label_NewAccount': _("Register new account"),
145 'label_autoconnect': _('Connect on frontend startup'),
146 'label_autodisconnect': _('Disconnect on frontend closure'),
147 'category_misc': _("Misc")
148 }
149
150 def load_default_params(self):
151 self.dom = minidom.parseString(Params.default_xml.encode('utf-8'))
152
153 def _mergeParams(self, source_node, dest_node):
154 """Look for every node in source_node and recursively copy them to dest if they don't exists"""
155
156 def getNodesMap(children):
157 ret = {}
158 for child in children:
159 if child.nodeType == child.ELEMENT_NODE:
160 ret[(child.tagName, child.getAttribute('name'))] = child
161 return ret
162 source_map = getNodesMap(source_node.childNodes)
163 dest_map = getNodesMap(dest_node.childNodes)
164 source_set = set(source_map.keys())
165 dest_set = set(dest_map.keys())
166 to_add = source_set.difference(dest_set)
167
168 for node_key in to_add:
169 dest_node.appendChild(source_map[node_key].cloneNode(True))
170
171 to_recurse = source_set - to_add
172 for node_key in to_recurse:
173 self._mergeParams(source_map[node_key], dest_map[node_key])
174
175 def load_xml(self, xml_file):
176 """Load parameters template from file"""
177 self.dom = minidom.parse(xml_file)
178 default_dom = minidom.parseString(Params.default_xml.encode('utf-8'))
179 self._mergeParams(default_dom.documentElement, self.dom.documentElement)
180
181 def loadGenParams(self):
182 """Load general parameters data from storage
183 @return: deferred triggered once params are loaded"""
184 return self.storage.loadGenParams(self.params_gen)
185
186 def loadIndParams(self, profile, cache=None):
187 """Load individual parameters
188 set self.params cache or a temporary cache
189 @param profile: profile to load (*must exist*)
190 @param cache: if not None, will be used to store the value, as a short time cache
191 @return: deferred triggered once params are loaded"""
192 if cache is None:
193 self.params[profile] = {}
194 return self.storage.loadIndParams(self.params[profile] if cache is None else cache, profile)
195
196 def purgeProfile(self, profile):
197 """Remove cache data of a profile
198 @param profile: %(doc_profile)s"""
199 try:
200 del self.params[profile]
201 except KeyError:
202 error(_("Trying to purge cache of a profile not in memory: [%s]") % profile)
203
204 def save_xml(self, filename):
205 """Save parameters template to xml file"""
206 with open(filename, 'wb') as xml_file:
207 xml_file.write(self.dom.toxml('utf-8'))
208
209 def __init__(self, host, storage):
210 debug("Parameters init")
211 self.host = host
212 self.storage = storage
213 self.default_profile = None
214 self.params = {}
215 self.params_gen = {}
216 host.registerCallback(host.registerNewAccountCB, with_data=True, force_id="registerNewAccount")
217
218 def asyncCreateProfile(self, profile):
219 """Create a new profile
220 @param profile: name of the profile
221 @param callback: called when the profile actually exists in database and memory
222 @return: a Deferred instance
223 """
224 if self.storage.hasProfile(profile):
225 info(_('The profile name already exists'))
226 return defer.fail(Failure(exceptions.ConflictError))
227 if not self.host.trigger.point("ProfileCreation", profile):
228 return defer.fail(Failure(exceptions.CancelError))
229 return self.storage.createProfile(profile)
230
231 def asyncDeleteProfile(self, profile, force=False):
232 """Delete an existing profile
233 @param profile: name of the profile
234 @param force: force the deletion even if the profile is connected.
235 To be used for direct calls only (not through the bridge).
236 @return: a Deferred instance
237 """
238 if not self.storage.hasProfile(profile):
239 info(_('Trying to delete an unknown profile'))
240 return defer.fail(Failure(exceptions.ProfileUnknownError))
241 if not force and self.host.isConnected(profile):
242 info(_("Trying to delete a connected profile"))
243 return defer.fail(Failure(exceptions.ConnectedProfileError))
244 return self.storage.deleteProfile(profile)
245
246 def getProfileName(self, profile_key, return_profile_keys = False):
247 """return profile according to profile_key
248 @param profile_key: profile name or key which can be
249 @ALL@ for all profiles
250 @DEFAULT@ for default profile
251 @param return_profile_keys: if True, return unmanaged profile keys (like "@ALL@"). This keys must be managed by the caller
252 @return: requested profile name or emptry string if it doesn't exist"""
253 if profile_key == '@DEFAULT@':
254 default = self.host.memory.memory_data.get('Profile_default')
255 if not default:
256 info(_('No default profile, returning first one')) # TODO: manage real default profile
257 try:
258 default = self.host.memory.memory_data['Profile_default'] = self.storage.getProfilesList()[0]
259 except IndexError:
260 info(_('No profile exist yet'))
261 return ""
262 return default # FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists
263 elif profile_key == '@NONE@':
264 raise exceptions.ProfileNotSetError
265 elif return_profile_keys and profile_key in ["@ALL@"]:
266 return profile_key # this value must be managed by the caller
267 if not self.storage.hasProfile(profile_key):
268 info(_('Trying to access an unknown profile'))
269 return "" # FIXME: raise exceptions.ProfileUnknownError here (must be well checked, this method is used in lot of places)
270 return profile_key
271
272 def __get_unique_node(self, parent, tag, name):
273 """return node with given tag
274 @param parent: parent of nodes to check (e.g. documentElement)
275 @param tag: tag to check (e.g. "category")
276 @param name: name to check (e.g. "JID")
277 @return: node if it exist or None
278 """
279 for node in parent.childNodes:
280 if node.nodeName == tag and node.getAttribute("name") == name:
281 #the node already exists
282 return node
283 #the node is new
284 return None
285
286 def updateParams(self, xml, security_limit=NO_SECURITY_LIMIT, app=''):
287 """import xml in parameters, update if the param already exists
288 If security_limit is specified and greater than -1, the parameters
289 that have a security level greater than security_limit are skipped.
290 @param xml: parameters in xml form
291 @param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure
292 @param app: name of the frontend registering the parameters or empty value
293 """
294 src_parent = minidom.parseString(xml.encode('utf-8')).documentElement
295
296 def pre_process_app_node(src_parent, security_limit, app):
297 """Parameters that are registered from a frontend must be checked"""
298 to_remove = []
299 for type_node in src_parent.childNodes:
300 if type_node.nodeName != INDIVIDUAL:
301 to_remove.append(type_node) # accept individual parameters only
302 continue
303 for cat_node in type_node.childNodes:
304 if cat_node.nodeName != 'category':
305 to_remove.append(cat_node)
306 continue
307 to_remove_count = 0 # count the params to be removed from current category
308 for node in cat_node.childNodes:
309 if node.nodeName != "param" or not self.checkSecurityLimit(node, security_limit):
310 to_remove.append(node)
311 to_remove_count += 1
312 continue
313 node.setAttribute('app', app)
314 if len(cat_node.childNodes) == to_remove_count: # remove empty category
315 for dummy in xrange(0, to_remove_count):
316 to_remove.pop()
317 to_remove.append(cat_node)
318 for node in to_remove:
319 node.parentNode.removeChild(node)
320
321 def import_node(tgt_parent, src_parent):
322 for child in src_parent.childNodes:
323 if child.nodeName == '#text':
324 continue
325 node = self.__get_unique_node(tgt_parent, child.nodeName, child.getAttribute("name"))
326 if not node: # The node is new
327 tgt_parent.appendChild(child)
328 else:
329 if child.nodeName == "param":
330 # The child updates an existing parameter, we replace the node
331 tgt_parent.replaceChild(child, node)
332 else:
333 # the node already exists, we recurse 1 more level
334 import_node(node, child)
335
336 if app:
337 pre_process_app_node(src_parent, security_limit, app)
338 import_node(self.dom.documentElement, src_parent)
339
340 def paramsRegisterApp(self, xml, security_limit, app):
341 """Register frontend's specific parameters
342 If security_limit is specified and greater than -1, the parameters
343 that have a security level greater than security_limit are skipped.
344 @param xml: XML definition of the parameters to be added
345 @param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure
346 @param app: name of the frontend registering the parameters
347 """
348 if not app:
349 warning(_("Trying to register frontends parameters with no specified app: aborted"))
350 return
351 if not hasattr(self, "frontends_cache"):
352 self.frontends_cache = []
353 if app in self.frontends_cache:
354 debug(_("Trying to register twice frontends parameters for %(app)s: aborted" % {"app": app}))
355 return
356 self.frontends_cache.append(app)
357 self.updateParams(xml, security_limit, app)
358 debug("Frontends parameters registered for %(app)s" % {'app': app})
359
360 def __default_ok(self, value, name, category):
361 #FIXME: will not work with individual parameters
362 self.setParam(name, value, category)
363
364 def __default_ko(self, failure, name, category):
365 error(_("Can't determine default value for [%(category)s/%(name)s]: %(reason)s") % {'category': category, 'name': name, 'reason': str(failure.value)})
366
367 def setDefault(self, name, category, callback, errback=None):
368 """Set default value of parameter
369 'default_cb' attibute of parameter must be set to 'yes'
370 @param name: name of the parameter
371 @param category: category of the parameter
372 @param callback: must return a string with the value (use deferred if needed)
373 @param errback: must manage the error with args failure, name, category
374 """
375 #TODO: send signal param update if value changed
376 #TODO: manage individual paramaters
377 debug ("setDefault called for %(category)s/%(name)s" % {"category": category, "name": name})
378 node = self._getParamNode(name, category, '@ALL@')
379 if not node:
380 error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
381 return
382 if node[1].getAttribute('default_cb') == 'yes':
383 # del node[1].attributes['default_cb'] # default_cb is not used anymore as a flag to know if we have to set the default value,
384 # and we can still use it later e.g. to call a generic setDefault method
385 value = self._getParam(category, name, GENERAL)
386 if value is None: # no value set by the user: we have the default value
387 debug ("Default value to set, using callback")
388 d = defer.maybeDeferred(callback)
389 d.addCallback(self.__default_ok, name, category)
390 d.addErrback(errback or self.__default_ko, name, category)
391
392 def _getAttr(self, node, attr, value):
393 """ get attribute value
394 @param node: XML param node
395 @param attr: name of the attribute to get (e.g.: 'value' or 'type')
396 @param value: user defined value"""
397 if attr == 'value':
398 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
399 if node.getAttribute('type') == 'bool':
400 return value_to_use.lower() not in ('false', '0', 'no')
401 return value_to_use
402 return node.getAttribute(attr)
403
404 def __type_to_string(self, result):
405 """ convert result to string, according to its type """
406 if isinstance(result, bool):
407 return "true" if result else "false"
408 return result
409
410 def getStringParamA(self, name, category, attr="value", profile_key="@NONE@"):
411 """ Same as getParamA but for bridge: convert non string value to string """
412 return self.__type_to_string(self.getParamA(name, category, attr, profile_key))
413
414 def getParamA(self, name, category, attr="value", profile_key="@NONE@"):
415 """Helper method to get a specific attribute
416 @param name: name of the parameter
417 @param category: category of the parameter
418 @param attr: name of the attribute (default: "value")
419 @param profile: owner of the param (@ALL@ for everyone)
420
421 @return: attribute"""
422 #FIXME: looks really dirty and buggy, need to be reviewed/refactored
423 node = self._getParamNode(name, category)
424 if not node:
425 error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
426 raise exceptions.NotFound
427
428 if node[0] == GENERAL:
429 value = self._getParam(category, name, GENERAL)
430 return self._getAttr(node[1], attr, value)
431
432 assert node[0] == INDIVIDUAL
433
434 profile = self.getProfileName(profile_key)
435 if not profile:
436 error(_('Requesting a param for an non-existant profile'))
437 raise exceptions.ProfileUnknownError
438
439 if profile not in self.params:
440 error(_('Requesting synchronous param for not connected profile'))
441 raise exceptions.NotConnectedProfileError(profile)
442
443 if attr == "value":
444 value = self._getParam(category, name, profile=profile)
445 return self._getAttr(node[1], attr, value)
446
447 def asyncGetStringParamA(self, name, category, attr="value", security_limit=NO_SECURITY_LIMIT, profile_key="@NONE@"):
448 d = self.asyncGetParamA(name, category, attr, security_limit, profile_key)
449 d.addCallback(self.__type_to_string)
450 return d
451
452 def asyncGetParamA(self, name, category, attr="value", security_limit=NO_SECURITY_LIMIT, profile_key="@NONE@"):
453 """Helper method to get a specific attribute
454 @param name: name of the parameter
455 @param category: category of the parameter
456 @param attr: name of the attribute (default: "value")
457 @param profile: owner of the param (@ALL@ for everyone)"""
458 node = self._getParamNode(name, category)
459 if not node:
460 error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
461 return None
462
463 if not self.checkSecurityLimit(node[1], security_limit):
464 warning(_("Trying to get parameter '%(param)s' in category '%(cat)s' without authorization!!!"
465 % {'param': name, 'cat': category}))
466 return None
467
468 if node[0] == GENERAL:
469 value = self._getParam(category, name, GENERAL)
470 return defer.succeed(self._getAttr(node[1], attr, value))
471
472 assert node[0] == INDIVIDUAL
473
474 profile = self.getProfileName(profile_key)
475 if not profile:
476 error(_('Requesting a param for a non-existant profile'))
477 return defer.fail()
478
479 if attr != "value":
480 return defer.succeed(node[1].getAttribute(attr))
481 try:
482 value = self._getParam(category, name, profile=profile)
483 return defer.succeed(self._getAttr(node[1], attr, value))
484 except exceptions.ProfileNotInCacheError:
485 #We have to ask data to the storage manager
486 d = self.storage.getIndParam(category, name, profile)
487 return d.addCallback(lambda value: self._getAttr(node[1], attr, value))
488
489 def _getParam(self, category, name, type_=INDIVIDUAL, cache=None, profile="@NONE@"):
490 """Return the param, or None if it doesn't exist
491 @param category: param category
492 @param name: param name
493 @param type_: GENERAL or INDIVIDUAL
494 @param cache: temporary cache, to use when profile is not logged
495 @param profile: the profile name (not profile key, i.e. name and not something like @DEFAULT@)
496 @return: param value or None if it doesn't exist
497 """
498 if type_ == GENERAL:
499 if (category, name) in self.params_gen:
500 return self.params_gen[(category, name)]
501 return None # This general param has the default value
502 assert (type_ == INDIVIDUAL)
503 if profile == "@NONE@":
504 raise exceptions.ProfileNotSetError
505 if profile in self.params:
506 cache = self.params[profile] # if profile is in main cache, we use it,
507 # ignoring the temporary cache
508 elif cache is None: # else we use the temporary cache if it exists, or raise an exception
509 raise exceptions.ProfileNotInCacheError
510 if (category, name) not in cache:
511 return None
512 return cache[(category, name)]
513
514 def __constructProfileXml(self, security_limit, app, profile):
515 """Construct xml for asked profile, filling values when needed
516 /!\ as noticed in doc, don't forget to unlink the minidom.Document
517 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
518 Otherwise sole the params which have a security level defined *and*
519 lower or equal to the specified value are returned.
520 @param app: name of the frontend requesting the parameters, or '' to get all parameters
521 @param profile: profile name (not key !)
522 @return: a deferred that fire a minidom.Document of the profile xml (cf warning above)
523 """
524
525 def checkNode(node):
526 """Check the node against security_limit and app"""
527 return self.checkSecurityLimit(node, security_limit) and self.checkApp(node, app)
528
529 def constructProfile(ignore, profile_cache):
530 # init the result document
531 prof_xml = minidom.parseString('<params/>')
532 cache = {}
533
534 for type_node in self.dom.documentElement.childNodes:
535 if type_node.nodeName != GENERAL and type_node.nodeName != INDIVIDUAL:
536 continue
537 # we use all params, general and individual
538 for cat_node in type_node.childNodes:
539 if cat_node.nodeName != 'category':
540 continue
541 category = cat_node.getAttribute('name')
542 dest_params = {} # result (merged) params for category
543 if category not in cache:
544 # we make a copy for the new xml
545 cache[category] = dest_cat = cat_node.cloneNode(True)
546 for node in dest_cat.childNodes:
547 if node.nodeName != "param":
548 continue
549 if not checkNode(node):
550 dest_cat.removeChild(node)
551 continue
552 dest_params[node.getAttribute('name')] = node
553 new_node = True
554 else:
555 # It's not a new node, we use the previously cloned one
556 dest_cat = cache[category]
557 new_node = False
558 params = cat_node.getElementsByTagName("param")
559
560 for param_node in params:
561 # we have to merge new params (we are parsing individual parameters, we have to add them
562 # to the previously parsed general ones)
563 name = param_node.getAttribute('name')
564 if not checkNode(param_node):
565 continue
566 if name not in dest_params:
567 # this is reached when a previous category exists
568 dest_params[name] = param_node.cloneNode(True)
569 dest_cat.appendChild(dest_params[name])
570
571 profile_value = self._getParam(category,
572 name, type_node.nodeName,
573 cache=profile_cache, profile=profile)
574 if profile_value is not None:
575 # there is a value for this profile, we must change the default
576 dest_params[name].setAttribute('value', profile_value)
577 if new_node:
578 prof_xml.documentElement.appendChild(dest_cat)
579
580 to_remove = []
581 for cat_node in prof_xml.documentElement.childNodes:
582 # we remove empty categories
583 if cat_node.getElementsByTagName("param").length == 0:
584 to_remove.append(cat_node)
585 for node in to_remove:
586 prof_xml.documentElement.removeChild(node)
587 return prof_xml
588
589 if profile in self.params:
590 d = defer.succeed(None)
591 profile_cache = self.params[profile]
592 else:
593 #profile is not in cache, we load values in a short time cache
594 profile_cache = {}
595 d = self.loadIndParams(profile, profile_cache)
596
597 return d.addCallback(constructProfile, profile_cache)
598
599 def getParamsUI(self, security_limit, app, profile_key):
600 """
601 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
602 Otherwise sole the params which have a security level defined *and*
603 lower or equal to the specified value are returned.
604 @param app: name of the frontend requesting the parameters, or '' to get all parameters
605 @param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile.
606 @return: a SàT XMLUI for parameters
607 """
608 profile = self.getProfileName(profile_key)
609 if not profile:
610 error(_("Asking params for inexistant profile"))
611 return ""
612 d = self.getParams(security_limit, app, profile)
613 return d.addCallback(lambda param_xml: paramsXML2XMLUI(param_xml))
614
615 def getParams(self, security_limit, app, profile_key):
616 """Construct xml for asked profile, take params xml as skeleton
617 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
618 Otherwise sole the params which have a security level defined *and*
619 lower or equal to the specified value are returned.
620 @param app: name of the frontend requesting the parameters, or '' to get all parameters
621 @param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile.
622 @return: XML of parameters
623 """
624 profile = self.getProfileName(profile_key)
625 if not profile:
626 error(_("Asking params for inexistant profile"))
627 return defer.succeed("")
628
629 def returnXML(prof_xml):
630 return_xml = prof_xml.toxml()
631 prof_xml.unlink()
632 return '\n'.join((line for line in return_xml.split('\n') if line))
633
634 return self.__constructProfileXml(security_limit, app, profile).addCallback(returnXML)
635
636 def getParamsForCategory(self, category, security_limit, app, profile_key):
637 """
638 @param category: the desired category
639 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
640 Otherwise sole the params which have a security level defined *and*
641 lower or equal to the specified value are returned.
642 @param app: name of the frontend requesting the parameters, or '' to get all parameters
643 @param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile.
644 @return: node's xml for selected category
645 """
646 #TODO: manage category of general type (without existant profile)
647 profile = self.getProfileName(profile_key)
648 if not profile:
649 error(_("Asking params for inexistant profile"))
650 return ""
651
652 def returnCategoryXml(prof_xml):
653 for node in prof_xml.getElementsByTagName("category"):
654 if node.nodeName == "category" and node.getAttribute("name") == category:
655 result = node.toxml()
656 prof_xml.unlink()
657 return result
658
659 prof_xml.unlink()
660 return "<category />"
661
662 d = self.__constructProfileXml(security_limit, app, profile)
663 return d.addCallback(returnCategoryXml)
664
665 def _getParamNode(self, name, category, type_="@ALL@"): # FIXME: is type_ useful ?
666 """Return a node from the param_xml
667 @param name: name of the node
668 @param category: category of the node
669 @type_: keyword for search:
670 @ALL@ search everywhere
671 @GENERAL@ only search in general type
672 @INDIVIDUAL@ only search in individual type
673 @return: a tuple with the node type and the the node, or None if not found"""
674
675 for type_node in self.dom.documentElement.childNodes:
676 if (((type_ == "@ALL@" or type_ == "@GENERAL@") and type_node.nodeName == GENERAL)
677 or ((type_ == "@ALL@" or type_ == "@INDIVIDUAL@") and type_node.nodeName == INDIVIDUAL)):
678 for node in type_node.getElementsByTagName('category'):
679 if node.getAttribute("name") == category:
680 params = node.getElementsByTagName("param")
681 for param in params:
682 if param.getAttribute("name") == name:
683 return (type_node.nodeName, param)
684 return None
685
686 def getParamsCategories(self):
687 """return the categories availables"""
688 categories = []
689 for cat in self.dom.getElementsByTagName("category"):
690 name = cat.getAttribute("name")
691 if name not in categories:
692 categories.append(cat.getAttribute("name"))
693 return categories
694
695 def setParam(self, name, value, category, security_limit=NO_SECURITY_LIMIT, profile_key='@NONE@'):
696 """Set a parameter, return None if the parameter is not in param xml"""
697 #TODO: use different behaviour depending of the data type (e.g. password encrypted)
698 if profile_key != "@NONE@":
699 profile = self.getProfileName(profile_key)
700 if not profile:
701 error(_('Trying to set parameter for an unknown profile'))
702 return # TODO: throw an error
703
704 node = self._getParamNode(name, category, '@ALL@')
705 if not node:
706 error(_('Requesting an unknown parameter (%(category)s/%(name)s)')
707 % {'category': category, 'name': name})
708 return
709
710 if not self.checkSecurityLimit(node[1], security_limit):
711 warning(_("Trying to set parameter '%(param)s' in category '%(cat)s' without authorization!!!"
712 % {'param': name, 'cat': category}))
713 return
714
715 if node[0] == GENERAL:
716 self.params_gen[(category, name)] = value
717 self.storage.setGenParam(category, name, value)
718 for profile in self.storage.getProfilesList():
719 if self.host.isConnected(profile):
720 self.host.bridge.paramUpdate(name, value, category, profile)
721 self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile)
722 return
723
724 assert (node[0] == INDIVIDUAL)
725 assert (profile_key != "@NONE@")
726
727 type_ = node[1].getAttribute("type")
728 if type_ == "button":
729 print "clique", node.toxml()
730 else:
731 if self.host.isConnected(profile): # key can not exists if profile is not connected
732 self.params[profile][(category, name)] = value
733 self.host.bridge.paramUpdate(name, value, category, profile)
734 self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile)
735 self.storage.setIndParam(category, name, value, profile)
736
737 def checkSecurityLimit(self, node, security_limit):
738 """Check the given node against the given security limit.
739 The value NO_SECURITY_LIMIT (-1) means that everything is allowed.
740 @return: True if this node can be accessed with the given security limit.
741 """
742 if security_limit < 0:
743 return True
744 if node.hasAttribute("security"):
745 if int(node.getAttribute("security")) <= security_limit:
746 return True
747 return False
748
749 def checkApp(self, node, app):
750 """Check the given node against the given app.
751 @param node: parameter node
752 @param app: name of the frontend requesting the parameters, or '' to get all parameters
753 @return: True if this node concerns the given app.
754 """
755 if not app or not node.hasAttribute("app"):
756 return True
757 return node.getAttribute("app") == app
758 106
759 107
760 class Memory(object): 108 class Memory(object):
761 """This class manage all persistent informations""" 109 """This class manage all persistent informations"""
762 110
768 # /!\ an entity is not necessarily in roster 116 # /!\ an entity is not necessarily in roster
769 self.subscriptions = {} 117 self.subscriptions = {}
770 self.server_features = {} # used to store discovery's informations 118 self.server_features = {} # used to store discovery's informations
771 self.server_identities = {} 119 self.server_identities = {}
772 self.config = self.parseMainConf() 120 self.config = self.parseMainConf()
773 host.setConst('savefile_database', SAVEFILE_DATABASE) 121 host.setConst('savefile_database', C.SAVEFILE_DATABASE)
774 database_file = os.path.expanduser(self.getConfig('', 'local_dir') + 122 database_file = os.path.expanduser(self.getConfig('', 'local_dir') +
775 self.host.getConst('savefile_database')) 123 self.host.getConst('savefile_database'))
776 self.storage = SqliteStorage(database_file, host.__version__) 124 self.storage = SqliteStorage(database_file, host.__version__)
777 PersistentDict.storage = self.storage 125 PersistentDict.storage = self.storage
778 self.params = Params(host, self.storage) 126 self.params = Params(host, self.storage)
1129 return self.params.getStringParamA(name, category, attr, profile_key) 477 return self.params.getStringParamA(name, category, attr, profile_key)
1130 478
1131 def getParamA(self, name, category, attr="value", profile_key='@NONE@'): 479 def getParamA(self, name, category, attr="value", profile_key='@NONE@'):
1132 return self.params.getParamA(name, category, attr, profile_key) 480 return self.params.getParamA(name, category, attr, profile_key)
1133 481
1134 def asyncGetParamA(self, name, category, attr="value", security_limit=NO_SECURITY_LIMIT, profile_key='@NONE@'): 482 def asyncGetParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key='@NONE@'):
1135 return self.params.asyncGetParamA(name, category, attr, security_limit, profile_key) 483 return self.params.asyncGetParamA(name, category, attr, security_limit, profile_key)
1136 484
1137 def asyncGetStringParamA(self, name, category, attr="value", security_limit=NO_SECURITY_LIMIT, profile_key='@NONE@'): 485 def asyncGetStringParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key='@NONE@'):
1138 return self.params.asyncGetStringParamA(name, category, attr, security_limit, profile_key) 486 return self.params.asyncGetStringParamA(name, category, attr, security_limit, profile_key)
1139 487
1140 def getParamsUI(self, security_limit=NO_SECURITY_LIMIT, app='', profile_key='@NONE@'): 488 def getParamsUI(self, security_limit=C.NO_SECURITY_LIMIT, app='', profile_key='@NONE@'):
1141 return self.params.getParamsUI(security_limit, app, profile_key) 489 return self.params.getParamsUI(security_limit, app, profile_key)
1142 490
1143 def getParams(self, security_limit=NO_SECURITY_LIMIT, app='', profile_key='@NONE@'): 491 def getParams(self, security_limit=C.NO_SECURITY_LIMIT, app='', profile_key='@NONE@'):
1144 return self.params.getParams(security_limit, app, profile_key) 492 return self.params.getParams(security_limit, app, profile_key)
1145 493
1146 def getParamsForCategory(self, category, security_limit=NO_SECURITY_LIMIT, app='', profile_key='@NONE@'): 494 def getParamsForCategory(self, category, security_limit=C.NO_SECURITY_LIMIT, app='', profile_key='@NONE@'):
1147 return self.params.getParamsForCategory(category, security_limit, app, profile_key) 495 return self.params.getParamsForCategory(category, security_limit, app, profile_key)
1148 496
1149 def getParamsCategories(self): 497 def getParamsCategories(self):
1150 return self.params.getParamsCategories() 498 return self.params.getParamsCategories()
1151 499
1152 def setParam(self, name, value, category, security_limit=NO_SECURITY_LIMIT, profile_key='@NONE@'): 500 def setParam(self, name, value, category, security_limit=C.NO_SECURITY_LIMIT, profile_key='@NONE@'):
1153 return self.params.setParam(name, value, category, security_limit, profile_key) 501 return self.params.setParam(name, value, category, security_limit, profile_key)
1154 502
1155 def updateParams(self, xml): 503 def updateParams(self, xml):
1156 return self.params.updateParams(xml) 504 return self.params.updateParams(xml)
1157 505
1158 def paramsRegisterApp(self, xml, security_limit=NO_SECURITY_LIMIT, app=''): 506 def paramsRegisterApp(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=''):
1159 return self.params.paramsRegisterApp(xml, security_limit, app) 507 return self.params.paramsRegisterApp(xml, security_limit, app)
1160 508
1161 def setDefault(self, name, category, callback, errback=None): 509 def setDefault(self, name, category, callback, errback=None):
1162 return self.params.setDefault(name, category, callback, errback) 510 return self.params.setDefault(name, category, callback, errback)