Mercurial > libervia-backend
comparison src/memory/params.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 | src/memory/memory.py@fab49a1d5ea2 |
children | 1a759096ccbd |
comparison
equal
deleted
inserted
replaced
913:d08cbdd566e2 | 914:1a3ba959f0ab |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SAT: a jabber client | |
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
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/>. | |
19 | |
20 from sat.core.i18n import _ | |
21 | |
22 from sat.core import exceptions | |
23 from sat.core.constants import Const as C | |
24 from xml.dom import minidom | |
25 from logging import debug, info, warning, error | |
26 from twisted.internet import defer | |
27 from twisted.python.failure import Failure | |
28 from sat.tools.xml_tools import paramsXML2XMLUI | |
29 | |
30 | |
31 class Params(object): | |
32 """This class manage parameters with xml""" | |
33 ### TODO: add desciption in params | |
34 | |
35 #TODO: move Watched in a plugin | |
36 #TODO: when priority is changed, a new presence stanza must be emitted | |
37 #TODO: int type (Priority should be int instead of string) | |
38 default_xml = u""" | |
39 <params> | |
40 <general> | |
41 </general> | |
42 <individual> | |
43 <category name="Connection" label="%(category_connection)s"> | |
44 <param name="JabberID" value="name@example.org/SàT" type="string" /> | |
45 <param name="Password" value="" type="password" /> | |
46 <param name="Priority" value="50" type="string" /> | |
47 <param name="Server" value="example.org" type="string" /> | |
48 <param name="Port" value="5222" type="string" /> | |
49 <param name="NewAccount" label="%(label_NewAccount)s" type="button" callback_id="registerNewAccount"/> | |
50 <param name="autoconnect" label="%(label_autoconnect)s" value="true" type="bool" /> | |
51 <param name="autodisconnect" label="%(label_autodisconnect)s" value="false" type="bool" /> | |
52 </category> | |
53 <category name="Misc" label="%(category_misc)s"> | |
54 <param name="Watched" value="test@Jabber.goffi.int" type="string" /> | |
55 </category> | |
56 </individual> | |
57 </params> | |
58 """ % { | |
59 'category_connection': _("Connection"), | |
60 'label_NewAccount': _("Register new account"), | |
61 'label_autoconnect': _('Connect on frontend startup'), | |
62 'label_autodisconnect': _('Disconnect on frontend closure'), | |
63 'category_misc': _("Misc") | |
64 } | |
65 | |
66 def load_default_params(self): | |
67 self.dom = minidom.parseString(Params.default_xml.encode('utf-8')) | |
68 | |
69 def _mergeParams(self, source_node, dest_node): | |
70 """Look for every node in source_node and recursively copy them to dest if they don't exists""" | |
71 | |
72 def getNodesMap(children): | |
73 ret = {} | |
74 for child in children: | |
75 if child.nodeType == child.ELEMENT_NODE: | |
76 ret[(child.tagName, child.getAttribute('name'))] = child | |
77 return ret | |
78 source_map = getNodesMap(source_node.childNodes) | |
79 dest_map = getNodesMap(dest_node.childNodes) | |
80 source_set = set(source_map.keys()) | |
81 dest_set = set(dest_map.keys()) | |
82 to_add = source_set.difference(dest_set) | |
83 | |
84 for node_key in to_add: | |
85 dest_node.appendChild(source_map[node_key].cloneNode(True)) | |
86 | |
87 to_recurse = source_set - to_add | |
88 for node_key in to_recurse: | |
89 self._mergeParams(source_map[node_key], dest_map[node_key]) | |
90 | |
91 def load_xml(self, xml_file): | |
92 """Load parameters template from file""" | |
93 self.dom = minidom.parse(xml_file) | |
94 default_dom = minidom.parseString(Params.default_xml.encode('utf-8')) | |
95 self._mergeParams(default_dom.documentElement, self.dom.documentElement) | |
96 | |
97 def loadGenParams(self): | |
98 """Load general parameters data from storage | |
99 @return: deferred triggered once params are loaded""" | |
100 return self.storage.loadGenParams(self.params_gen) | |
101 | |
102 def loadIndParams(self, profile, cache=None): | |
103 """Load individual parameters | |
104 set self.params cache or a temporary cache | |
105 @param profile: profile to load (*must exist*) | |
106 @param cache: if not None, will be used to store the value, as a short time cache | |
107 @return: deferred triggered once params are loaded""" | |
108 if cache is None: | |
109 self.params[profile] = {} | |
110 return self.storage.loadIndParams(self.params[profile] if cache is None else cache, profile) | |
111 | |
112 def purgeProfile(self, profile): | |
113 """Remove cache data of a profile | |
114 @param profile: %(doc_profile)s""" | |
115 try: | |
116 del self.params[profile] | |
117 except KeyError: | |
118 error(_("Trying to purge cache of a profile not in memory: [%s]") % profile) | |
119 | |
120 def save_xml(self, filename): | |
121 """Save parameters template to xml file""" | |
122 with open(filename, 'wb') as xml_file: | |
123 xml_file.write(self.dom.toxml('utf-8')) | |
124 | |
125 def __init__(self, host, storage): | |
126 debug("Parameters init") | |
127 self.host = host | |
128 self.storage = storage | |
129 self.default_profile = None | |
130 self.params = {} | |
131 self.params_gen = {} | |
132 host.registerCallback(host.registerNewAccountCB, with_data=True, force_id="registerNewAccount") | |
133 | |
134 def asyncCreateProfile(self, profile): | |
135 """Create a new profile | |
136 @param profile: name of the profile | |
137 @param callback: called when the profile actually exists in database and memory | |
138 @return: a Deferred instance | |
139 """ | |
140 if self.storage.hasProfile(profile): | |
141 info(_('The profile name already exists')) | |
142 return defer.fail(Failure(exceptions.ConflictError)) | |
143 if not self.host.trigger.point("ProfileCreation", profile): | |
144 return defer.fail(Failure(exceptions.CancelError)) | |
145 return self.storage.createProfile(profile) | |
146 | |
147 def asyncDeleteProfile(self, profile, force=False): | |
148 """Delete an existing profile | |
149 @param profile: name of the profile | |
150 @param force: force the deletion even if the profile is connected. | |
151 To be used for direct calls only (not through the bridge). | |
152 @return: a Deferred instance | |
153 """ | |
154 if not self.storage.hasProfile(profile): | |
155 info(_('Trying to delete an unknown profile')) | |
156 return defer.fail(Failure(exceptions.ProfileUnknownError)) | |
157 if not force and self.host.isConnected(profile): | |
158 info(_("Trying to delete a connected profile")) | |
159 return defer.fail(Failure(exceptions.ConnectedProfileError)) | |
160 return self.storage.deleteProfile(profile) | |
161 | |
162 def getProfileName(self, profile_key, return_profile_keys = False): | |
163 """return profile according to profile_key | |
164 @param profile_key: profile name or key which can be | |
165 @ALL@ for all profiles | |
166 @DEFAULT@ for default profile | |
167 @param return_profile_keys: if True, return unmanaged profile keys (like "@ALL@"). This keys must be managed by the caller | |
168 @return: requested profile name or emptry string if it doesn't exist""" | |
169 if profile_key == '@DEFAULT@': | |
170 default = self.host.memory.memory_data.get('Profile_default') | |
171 if not default: | |
172 info(_('No default profile, returning first one')) # TODO: manage real default profile | |
173 try: | |
174 default = self.host.memory.memory_data['Profile_default'] = self.storage.getProfilesList()[0] | |
175 except IndexError: | |
176 info(_('No profile exist yet')) | |
177 return "" | |
178 return default # FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists | |
179 elif profile_key == '@NONE@': | |
180 raise exceptions.ProfileNotSetError | |
181 elif return_profile_keys and profile_key in ["@ALL@"]: | |
182 return profile_key # this value must be managed by the caller | |
183 if not self.storage.hasProfile(profile_key): | |
184 info(_('Trying to access an unknown profile')) | |
185 return "" # FIXME: raise exceptions.ProfileUnknownError here (must be well checked, this method is used in lot of places) | |
186 return profile_key | |
187 | |
188 def __get_unique_node(self, parent, tag, name): | |
189 """return node with given tag | |
190 @param parent: parent of nodes to check (e.g. documentElement) | |
191 @param tag: tag to check (e.g. "category") | |
192 @param name: name to check (e.g. "JID") | |
193 @return: node if it exist or None | |
194 """ | |
195 for node in parent.childNodes: | |
196 if node.nodeName == tag and node.getAttribute("name") == name: | |
197 #the node already exists | |
198 return node | |
199 #the node is new | |
200 return None | |
201 | |
202 def updateParams(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=''): | |
203 """import xml in parameters, update if the param already exists | |
204 If security_limit is specified and greater than -1, the parameters | |
205 that have a security level greater than security_limit are skipped. | |
206 @param xml: parameters in xml form | |
207 @param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure | |
208 @param app: name of the frontend registering the parameters or empty value | |
209 """ | |
210 src_parent = minidom.parseString(xml.encode('utf-8')).documentElement | |
211 | |
212 def pre_process_app_node(src_parent, security_limit, app): | |
213 """Parameters that are registered from a frontend must be checked""" | |
214 to_remove = [] | |
215 for type_node in src_parent.childNodes: | |
216 if type_node.nodeName != C.INDIVIDUAL: | |
217 to_remove.append(type_node) # accept individual parameters only | |
218 continue | |
219 for cat_node in type_node.childNodes: | |
220 if cat_node.nodeName != 'category': | |
221 to_remove.append(cat_node) | |
222 continue | |
223 to_remove_count = 0 # count the params to be removed from current category | |
224 for node in cat_node.childNodes: | |
225 if node.nodeName != "param" or not self.checkSecurityLimit(node, security_limit): | |
226 to_remove.append(node) | |
227 to_remove_count += 1 | |
228 continue | |
229 node.setAttribute('app', app) | |
230 if len(cat_node.childNodes) == to_remove_count: # remove empty category | |
231 for dummy in xrange(0, to_remove_count): | |
232 to_remove.pop() | |
233 to_remove.append(cat_node) | |
234 for node in to_remove: | |
235 node.parentNode.removeChild(node) | |
236 | |
237 def import_node(tgt_parent, src_parent): | |
238 for child in src_parent.childNodes: | |
239 if child.nodeName == '#text': | |
240 continue | |
241 node = self.__get_unique_node(tgt_parent, child.nodeName, child.getAttribute("name")) | |
242 if not node: # The node is new | |
243 tgt_parent.appendChild(child) | |
244 else: | |
245 if child.nodeName == "param": | |
246 # The child updates an existing parameter, we replace the node | |
247 tgt_parent.replaceChild(child, node) | |
248 else: | |
249 # the node already exists, we recurse 1 more level | |
250 import_node(node, child) | |
251 | |
252 if app: | |
253 pre_process_app_node(src_parent, security_limit, app) | |
254 import_node(self.dom.documentElement, src_parent) | |
255 | |
256 def paramsRegisterApp(self, xml, security_limit, app): | |
257 """Register frontend's specific parameters | |
258 If security_limit is specified and greater than -1, the parameters | |
259 that have a security level greater than security_limit are skipped. | |
260 @param xml: XML definition of the parameters to be added | |
261 @param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure | |
262 @param app: name of the frontend registering the parameters | |
263 """ | |
264 if not app: | |
265 warning(_("Trying to register frontends parameters with no specified app: aborted")) | |
266 return | |
267 if not hasattr(self, "frontends_cache"): | |
268 self.frontends_cache = [] | |
269 if app in self.frontends_cache: | |
270 debug(_("Trying to register twice frontends parameters for %(app)s: aborted" % {"app": app})) | |
271 return | |
272 self.frontends_cache.append(app) | |
273 self.updateParams(xml, security_limit, app) | |
274 debug("Frontends parameters registered for %(app)s" % {'app': app}) | |
275 | |
276 def __default_ok(self, value, name, category): | |
277 #FIXME: will not work with individual parameters | |
278 self.setParam(name, value, category) | |
279 | |
280 def __default_ko(self, failure, name, category): | |
281 error(_("Can't determine default value for [%(category)s/%(name)s]: %(reason)s") % {'category': category, 'name': name, 'reason': str(failure.value)}) | |
282 | |
283 def setDefault(self, name, category, callback, errback=None): | |
284 """Set default value of parameter | |
285 'default_cb' attibute of parameter must be set to 'yes' | |
286 @param name: name of the parameter | |
287 @param category: category of the parameter | |
288 @param callback: must return a string with the value (use deferred if needed) | |
289 @param errback: must manage the error with args failure, name, category | |
290 """ | |
291 #TODO: send signal param update if value changed | |
292 #TODO: manage individual paramaters | |
293 debug ("setDefault called for %(category)s/%(name)s" % {"category": category, "name": name}) | |
294 node = self._getParamNode(name, category, '@ALL@') | |
295 if not node: | |
296 error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category}) | |
297 return | |
298 if node[1].getAttribute('default_cb') == 'yes': | |
299 # 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, | |
300 # and we can still use it later e.g. to call a generic setDefault method | |
301 value = self._getParam(category, name, C.GENERAL) | |
302 if value is None: # no value set by the user: we have the default value | |
303 debug ("Default value to set, using callback") | |
304 d = defer.maybeDeferred(callback) | |
305 d.addCallback(self.__default_ok, name, category) | |
306 d.addErrback(errback or self.__default_ko, name, category) | |
307 | |
308 def _getAttr(self, node, attr, value): | |
309 """ get attribute value | |
310 @param node: XML param node | |
311 @param attr: name of the attribute to get (e.g.: 'value' or 'type') | |
312 @param value: user defined value""" | |
313 if attr == 'value': | |
314 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 | |
315 if node.getAttribute('type') == 'bool': | |
316 return value_to_use.lower() not in ('false', '0', 'no') | |
317 return value_to_use | |
318 return node.getAttribute(attr) | |
319 | |
320 def __type_to_string(self, result): | |
321 """ convert result to string, according to its type """ | |
322 if isinstance(result, bool): | |
323 return "true" if result else "false" | |
324 return result | |
325 | |
326 def getStringParamA(self, name, category, attr="value", profile_key="@NONE@"): | |
327 """ Same as getParamA but for bridge: convert non string value to string """ | |
328 return self.__type_to_string(self.getParamA(name, category, attr, profile_key)) | |
329 | |
330 def getParamA(self, name, category, attr="value", profile_key="@NONE@"): | |
331 """Helper method to get a specific attribute | |
332 @param name: name of the parameter | |
333 @param category: category of the parameter | |
334 @param attr: name of the attribute (default: "value") | |
335 @param profile: owner of the param (@ALL@ for everyone) | |
336 | |
337 @return: attribute""" | |
338 #FIXME: looks really dirty and buggy, need to be reviewed/refactored | |
339 node = self._getParamNode(name, category) | |
340 if not node: | |
341 error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category}) | |
342 raise exceptions.NotFound | |
343 | |
344 if node[0] == C.GENERAL: | |
345 value = self._getParam(category, name, C.GENERAL) | |
346 return self._getAttr(node[1], attr, value) | |
347 | |
348 assert node[0] == C.INDIVIDUAL | |
349 | |
350 profile = self.getProfileName(profile_key) | |
351 if not profile: | |
352 error(_('Requesting a param for an non-existant profile')) | |
353 raise exceptions.ProfileUnknownError | |
354 | |
355 if profile not in self.params: | |
356 error(_('Requesting synchronous param for not connected profile')) | |
357 raise exceptions.NotConnectedProfileError(profile) | |
358 | |
359 if attr == "value": | |
360 value = self._getParam(category, name, profile=profile) | |
361 return self._getAttr(node[1], attr, value) | |
362 | |
363 def asyncGetStringParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key="@NONE@"): | |
364 d = self.asyncGetParamA(name, category, attr, security_limit, profile_key) | |
365 d.addCallback(self.__type_to_string) | |
366 return d | |
367 | |
368 def asyncGetParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key="@NONE@"): | |
369 """Helper method to get a specific attribute | |
370 @param name: name of the parameter | |
371 @param category: category of the parameter | |
372 @param attr: name of the attribute (default: "value") | |
373 @param profile: owner of the param (@ALL@ for everyone)""" | |
374 node = self._getParamNode(name, category) | |
375 if not node: | |
376 error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category}) | |
377 return None | |
378 | |
379 if not self.checkSecurityLimit(node[1], security_limit): | |
380 warning(_("Trying to get parameter '%(param)s' in category '%(cat)s' without authorization!!!" | |
381 % {'param': name, 'cat': category})) | |
382 return None | |
383 | |
384 if node[0] == C.GENERAL: | |
385 value = self._getParam(category, name, C.GENERAL) | |
386 return defer.succeed(self._getAttr(node[1], attr, value)) | |
387 | |
388 assert node[0] == C.INDIVIDUAL | |
389 | |
390 profile = self.getProfileName(profile_key) | |
391 if not profile: | |
392 error(_('Requesting a param for a non-existant profile')) | |
393 return defer.fail() | |
394 | |
395 if attr != "value": | |
396 return defer.succeed(node[1].getAttribute(attr)) | |
397 try: | |
398 value = self._getParam(category, name, profile=profile) | |
399 return defer.succeed(self._getAttr(node[1], attr, value)) | |
400 except exceptions.ProfileNotInCacheError: | |
401 #We have to ask data to the storage manager | |
402 d = self.storage.getIndParam(category, name, profile) | |
403 return d.addCallback(lambda value: self._getAttr(node[1], attr, value)) | |
404 | |
405 def _getParam(self, category, name, type_=C.INDIVIDUAL, cache=None, profile="@NONE@"): | |
406 """Return the param, or None if it doesn't exist | |
407 @param category: param category | |
408 @param name: param name | |
409 @param type_: GENERAL or INDIVIDUAL | |
410 @param cache: temporary cache, to use when profile is not logged | |
411 @param profile: the profile name (not profile key, i.e. name and not something like @DEFAULT@) | |
412 @return: param value or None if it doesn't exist | |
413 """ | |
414 if type_ == C.GENERAL: | |
415 if (category, name) in self.params_gen: | |
416 return self.params_gen[(category, name)] | |
417 return None # This general param has the default value | |
418 assert (type_ == C.INDIVIDUAL) | |
419 if profile == "@NONE@": | |
420 raise exceptions.ProfileNotSetError | |
421 if profile in self.params: | |
422 cache = self.params[profile] # if profile is in main cache, we use it, | |
423 # ignoring the temporary cache | |
424 elif cache is None: # else we use the temporary cache if it exists, or raise an exception | |
425 raise exceptions.ProfileNotInCacheError | |
426 if (category, name) not in cache: | |
427 return None | |
428 return cache[(category, name)] | |
429 | |
430 def __constructProfileXml(self, security_limit, app, profile): | |
431 """Construct xml for asked profile, filling values when needed | |
432 /!\ as noticed in doc, don't forget to unlink the minidom.Document | |
433 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params. | |
434 Otherwise sole the params which have a security level defined *and* | |
435 lower or equal to the specified value are returned. | |
436 @param app: name of the frontend requesting the parameters, or '' to get all parameters | |
437 @param profile: profile name (not key !) | |
438 @return: a deferred that fire a minidom.Document of the profile xml (cf warning above) | |
439 """ | |
440 | |
441 def checkNode(node): | |
442 """Check the node against security_limit and app""" | |
443 return self.checkSecurityLimit(node, security_limit) and self.checkApp(node, app) | |
444 | |
445 def constructProfile(ignore, profile_cache): | |
446 # init the result document | |
447 prof_xml = minidom.parseString('<params/>') | |
448 cache = {} | |
449 | |
450 for type_node in self.dom.documentElement.childNodes: | |
451 if type_node.nodeName != C.GENERAL and type_node.nodeName != C.INDIVIDUAL: | |
452 continue | |
453 # we use all params, general and individual | |
454 for cat_node in type_node.childNodes: | |
455 if cat_node.nodeName != 'category': | |
456 continue | |
457 category = cat_node.getAttribute('name') | |
458 dest_params = {} # result (merged) params for category | |
459 if category not in cache: | |
460 # we make a copy for the new xml | |
461 cache[category] = dest_cat = cat_node.cloneNode(True) | |
462 for node in dest_cat.childNodes: | |
463 if node.nodeName != "param": | |
464 continue | |
465 if not checkNode(node): | |
466 dest_cat.removeChild(node) | |
467 continue | |
468 dest_params[node.getAttribute('name')] = node | |
469 new_node = True | |
470 else: | |
471 # It's not a new node, we use the previously cloned one | |
472 dest_cat = cache[category] | |
473 new_node = False | |
474 params = cat_node.getElementsByTagName("param") | |
475 | |
476 for param_node in params: | |
477 # we have to merge new params (we are parsing individual parameters, we have to add them | |
478 # to the previously parsed general ones) | |
479 name = param_node.getAttribute('name') | |
480 if not checkNode(param_node): | |
481 continue | |
482 if name not in dest_params: | |
483 # this is reached when a previous category exists | |
484 dest_params[name] = param_node.cloneNode(True) | |
485 dest_cat.appendChild(dest_params[name]) | |
486 | |
487 profile_value = self._getParam(category, | |
488 name, type_node.nodeName, | |
489 cache=profile_cache, profile=profile) | |
490 if profile_value is not None: | |
491 # there is a value for this profile, we must change the default | |
492 dest_params[name].setAttribute('value', profile_value) | |
493 if new_node: | |
494 prof_xml.documentElement.appendChild(dest_cat) | |
495 | |
496 to_remove = [] | |
497 for cat_node in prof_xml.documentElement.childNodes: | |
498 # we remove empty categories | |
499 if cat_node.getElementsByTagName("param").length == 0: | |
500 to_remove.append(cat_node) | |
501 for node in to_remove: | |
502 prof_xml.documentElement.removeChild(node) | |
503 return prof_xml | |
504 | |
505 if profile in self.params: | |
506 d = defer.succeed(None) | |
507 profile_cache = self.params[profile] | |
508 else: | |
509 #profile is not in cache, we load values in a short time cache | |
510 profile_cache = {} | |
511 d = self.loadIndParams(profile, profile_cache) | |
512 | |
513 return d.addCallback(constructProfile, profile_cache) | |
514 | |
515 def getParamsUI(self, security_limit, app, profile_key): | |
516 """ | |
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_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile. | |
522 @return: a SàT XMLUI for parameters | |
523 """ | |
524 profile = self.getProfileName(profile_key) | |
525 if not profile: | |
526 error(_("Asking params for inexistant profile")) | |
527 return "" | |
528 d = self.getParams(security_limit, app, profile) | |
529 return d.addCallback(lambda param_xml: paramsXML2XMLUI(param_xml)) | |
530 | |
531 def getParams(self, security_limit, app, profile_key): | |
532 """Construct xml for asked profile, take params xml as skeleton | |
533 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params. | |
534 Otherwise sole the params which have a security level defined *and* | |
535 lower or equal to the specified value are returned. | |
536 @param app: name of the frontend requesting the parameters, or '' to get all parameters | |
537 @param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile. | |
538 @return: XML of parameters | |
539 """ | |
540 profile = self.getProfileName(profile_key) | |
541 if not profile: | |
542 error(_("Asking params for inexistant profile")) | |
543 return defer.succeed("") | |
544 | |
545 def returnXML(prof_xml): | |
546 return_xml = prof_xml.toxml() | |
547 prof_xml.unlink() | |
548 return '\n'.join((line for line in return_xml.split('\n') if line)) | |
549 | |
550 return self.__constructProfileXml(security_limit, app, profile).addCallback(returnXML) | |
551 | |
552 def getParamsForCategory(self, category, security_limit, app, profile_key): | |
553 """ | |
554 @param category: the desired category | |
555 @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params. | |
556 Otherwise sole the params which have a security level defined *and* | |
557 lower or equal to the specified value are returned. | |
558 @param app: name of the frontend requesting the parameters, or '' to get all parameters | |
559 @param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile. | |
560 @return: node's xml for selected category | |
561 """ | |
562 #TODO: manage category of general type (without existant profile) | |
563 profile = self.getProfileName(profile_key) | |
564 if not profile: | |
565 error(_("Asking params for inexistant profile")) | |
566 return "" | |
567 | |
568 def returnCategoryXml(prof_xml): | |
569 for node in prof_xml.getElementsByTagName("category"): | |
570 if node.nodeName == "category" and node.getAttribute("name") == category: | |
571 result = node.toxml() | |
572 prof_xml.unlink() | |
573 return result | |
574 | |
575 prof_xml.unlink() | |
576 return "<category />" | |
577 | |
578 d = self.__constructProfileXml(security_limit, app, profile) | |
579 return d.addCallback(returnCategoryXml) | |
580 | |
581 def _getParamNode(self, name, category, type_="@ALL@"): # FIXME: is type_ useful ? | |
582 """Return a node from the param_xml | |
583 @param name: name of the node | |
584 @param category: category of the node | |
585 @type_: keyword for search: | |
586 @ALL@ search everywhere | |
587 @GENERAL@ only search in general type | |
588 @INDIVIDUAL@ only search in individual type | |
589 @return: a tuple with the node type and the the node, or None if not found""" | |
590 | |
591 for type_node in self.dom.documentElement.childNodes: | |
592 if (((type_ == "@ALL@" or type_ == "@GENERAL@") and type_node.nodeName == C.GENERAL) | |
593 or ((type_ == "@ALL@" or type_ == "@INDIVIDUAL@") and type_node.nodeName == C.INDIVIDUAL)): | |
594 for node in type_node.getElementsByTagName('category'): | |
595 if node.getAttribute("name") == category: | |
596 params = node.getElementsByTagName("param") | |
597 for param in params: | |
598 if param.getAttribute("name") == name: | |
599 return (type_node.nodeName, param) | |
600 return None | |
601 | |
602 def getParamsCategories(self): | |
603 """return the categories availables""" | |
604 categories = [] | |
605 for cat in self.dom.getElementsByTagName("category"): | |
606 name = cat.getAttribute("name") | |
607 if name not in categories: | |
608 categories.append(cat.getAttribute("name")) | |
609 return categories | |
610 | |
611 def setParam(self, name, value, category, security_limit=C.NO_SECURITY_LIMIT, profile_key='@NONE@'): | |
612 """Set a parameter, return None if the parameter is not in param xml""" | |
613 #TODO: use different behaviour depending of the data type (e.g. password encrypted) | |
614 if profile_key != "@NONE@": | |
615 profile = self.getProfileName(profile_key) | |
616 if not profile: | |
617 error(_('Trying to set parameter for an unknown profile')) | |
618 return # TODO: throw an error | |
619 | |
620 node = self._getParamNode(name, category, '@ALL@') | |
621 if not node: | |
622 error(_('Requesting an unknown parameter (%(category)s/%(name)s)') | |
623 % {'category': category, 'name': name}) | |
624 return | |
625 | |
626 if not self.checkSecurityLimit(node[1], security_limit): | |
627 warning(_("Trying to set parameter '%(param)s' in category '%(cat)s' without authorization!!!" | |
628 % {'param': name, 'cat': category})) | |
629 return | |
630 | |
631 if node[0] == C.GENERAL: | |
632 self.params_gen[(category, name)] = value | |
633 self.storage.setGenParam(category, name, value) | |
634 for profile in self.storage.getProfilesList(): | |
635 if self.host.isConnected(profile): | |
636 self.host.bridge.paramUpdate(name, value, category, profile) | |
637 self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile) | |
638 return | |
639 | |
640 assert (node[0] == C.INDIVIDUAL) | |
641 assert (profile_key != "@NONE@") | |
642 | |
643 type_ = node[1].getAttribute("type") | |
644 if type_ == "button": | |
645 print "clique", node.toxml() | |
646 else: | |
647 if self.host.isConnected(profile): # key can not exists if profile is not connected | |
648 self.params[profile][(category, name)] = value | |
649 self.host.bridge.paramUpdate(name, value, category, profile) | |
650 self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile) | |
651 self.storage.setIndParam(category, name, value, profile) | |
652 | |
653 def checkSecurityLimit(self, node, security_limit): | |
654 """Check the given node against the given security limit. | |
655 The value NO_SECURITY_LIMIT (-1) means that everything is allowed. | |
656 @return: True if this node can be accessed with the given security limit. | |
657 """ | |
658 if security_limit < 0: | |
659 return True | |
660 if node.hasAttribute("security"): | |
661 if int(node.getAttribute("security")) <= security_limit: | |
662 return True | |
663 return False | |
664 | |
665 def checkApp(self, node, app): | |
666 """Check the given node against the given app. | |
667 @param node: parameter node | |
668 @param app: name of the frontend requesting the parameters, or '' to get all parameters | |
669 @return: True if this node concerns the given app. | |
670 """ | |
671 if not app or not node.hasAttribute("app"): | |
672 return True | |
673 return node.getAttribute("app") == app |