Mercurial > libervia-backend
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) |