core (memory): moved Params in its own module + introduced a new core/constants module, and moved some constants there

This commit is contained in:
Goffi 2014-03-21 16:08:11 +01:00
commit e689f6bb34
5 changed files with 715 additions and 668 deletions

View file

@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Primitivus: a SAT frontend
# generic module for SàT frontends
# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
# This program is free software: you can redistribute it and/or modify

25
src/core/constants.py Normal file
View file

@ -0,0 +1,25 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# SàT: a XMPP client
# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
class Const(object):
NO_SECURITY_LIMIT = -1
INDIVIDUAL = "individual"
GENERAL = "general"
SAVEFILE_DATABASE = "/sat.db"

View file

@ -39,7 +39,8 @@ import os.path
from sat.core.default_config import CONST
from sat.core import xmpp
from sat.core import exceptions
from sat.memory.memory import Memory, NO_SECURITY_LIMIT
from sat.core.constants import Const as C
from sat.memory.memory import Memory
from sat.tools.xml_tools import tupleList2dataForm
from sat.tools.misc import TriggerManager
from glob import glob
@ -897,7 +898,7 @@ class SAT(service.Service):
#Menus management
def importMenu(self, path, callback, security_limit=NO_SECURITY_LIMIT, help_string="", type_="NORMAL"):
def importMenu(self, path, callback, security_limit=C.NO_SECURITY_LIMIT, help_string="", type_="NORMAL"):
"""register a new menu for frontends
@param path: path to go to the menu (category/subcategory/.../item), must be an iterable (e.g.: ("File", "Open"))
/!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open")))
@ -942,7 +943,7 @@ class SAT(service.Service):
return callback_id
def getMenus(self, language='', security_limit=NO_SECURITY_LIMIT):
def getMenus(self, language='', security_limit=C.NO_SECURITY_LIMIT):
"""Return all menus registered
@param language: language used for translation, or empty string for default
@param security_limit: %(doc_security_limit)s
@ -958,7 +959,7 @@ class SAT(service.Service):
type_ = menu_data['type']
path = menu_data['path']
menu_security_limit = menu_data['security_limit']
if security_limit!=NO_SECURITY_LIMIT and (menu_security_limit==NO_SECURITY_LIMIT or menu_security_limit>security_limit):
if security_limit!=C.NO_SECURITY_LIMIT and (menu_security_limit==C.NO_SECURITY_LIMIT or menu_security_limit>security_limit):
continue
languageSwitch(language)
path_i18n = [_(elt) for elt in path]

View file

@ -17,28 +17,21 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import with_statement
from sat.core.i18n import _
import os.path
import csv
from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError
from xml.dom import minidom
from uuid import uuid4
from logging import debug, info, warning, error
from twisted.internet import defer, reactor
from twisted.words.protocols.jabber import jid
from twisted.python.failure import Failure
from sat.tools.xml_tools import paramsXML2XMLUI
from sat.core import exceptions
from sat.core.constants import Const as C
from sat.core.default_config import default_config
from sat.memory.sqlite import SqliteStorage
from sat.memory.persistent import PersistentDict
from sat.core import exceptions
SAVEFILE_DATABASE = "/sat.db"
NO_SECURITY_LIMIT = -1
INDIVIDUAL = "individual"
GENERAL = "general"
from sat.memory.params import Params
class Sessions(object):
@ -112,651 +105,6 @@ class Sessions(object):
return self._sessions.iterkeys()
class Params(object):
"""This class manage parameters with xml"""
### TODO: add desciption in params
#TODO: move Watched in a plugin
#TODO: when priority is changed, a new presence stanza must be emitted
#TODO: int type (Priority should be int instead of string)
default_xml = u"""
<params>
<general>
</general>
<individual>
<category name="Connection" label="%(category_connection)s">
<param name="JabberID" value="name@example.org/SàT" type="string" />
<param name="Password" value="" type="password" />
<param name="Priority" value="50" type="string" />
<param name="Server" value="example.org" type="string" />
<param name="Port" value="5222" type="string" />
<param name="NewAccount" label="%(label_NewAccount)s" type="button" callback_id="registerNewAccount"/>
<param name="autoconnect" label="%(label_autoconnect)s" value="true" type="bool" />
<param name="autodisconnect" label="%(label_autodisconnect)s" value="false" type="bool" />
</category>
<category name="Misc" label="%(category_misc)s">
<param name="Watched" value="test@Jabber.goffi.int" type="string" />
</category>
</individual>
</params>
""" % {
'category_connection': _("Connection"),
'label_NewAccount': _("Register new account"),
'label_autoconnect': _('Connect on frontend startup'),
'label_autodisconnect': _('Disconnect on frontend closure'),
'category_misc': _("Misc")
}
def load_default_params(self):
self.dom = minidom.parseString(Params.default_xml.encode('utf-8'))
def _mergeParams(self, source_node, dest_node):
"""Look for every node in source_node and recursively copy them to dest if they don't exists"""
def getNodesMap(children):
ret = {}
for child in children:
if child.nodeType == child.ELEMENT_NODE:
ret[(child.tagName, child.getAttribute('name'))] = child
return ret
source_map = getNodesMap(source_node.childNodes)
dest_map = getNodesMap(dest_node.childNodes)
source_set = set(source_map.keys())
dest_set = set(dest_map.keys())
to_add = source_set.difference(dest_set)
for node_key in to_add:
dest_node.appendChild(source_map[node_key].cloneNode(True))
to_recurse = source_set - to_add
for node_key in to_recurse:
self._mergeParams(source_map[node_key], dest_map[node_key])
def load_xml(self, xml_file):
"""Load parameters template from file"""
self.dom = minidom.parse(xml_file)
default_dom = minidom.parseString(Params.default_xml.encode('utf-8'))
self._mergeParams(default_dom.documentElement, self.dom.documentElement)
def loadGenParams(self):
"""Load general parameters data from storage
@return: deferred triggered once params are loaded"""
return self.storage.loadGenParams(self.params_gen)
def loadIndParams(self, profile, cache=None):
"""Load individual parameters
set self.params cache or a temporary cache
@param profile: profile to load (*must exist*)
@param cache: if not None, will be used to store the value, as a short time cache
@return: deferred triggered once params are loaded"""
if cache is None:
self.params[profile] = {}
return self.storage.loadIndParams(self.params[profile] if cache is None else cache, profile)
def purgeProfile(self, profile):
"""Remove cache data of a profile
@param profile: %(doc_profile)s"""
try:
del self.params[profile]
except KeyError:
error(_("Trying to purge cache of a profile not in memory: [%s]") % profile)
def save_xml(self, filename):
"""Save parameters template to xml file"""
with open(filename, 'wb') as xml_file:
xml_file.write(self.dom.toxml('utf-8'))
def __init__(self, host, storage):
debug("Parameters init")
self.host = host
self.storage = storage
self.default_profile = None
self.params = {}
self.params_gen = {}
host.registerCallback(host.registerNewAccountCB, with_data=True, force_id="registerNewAccount")
def asyncCreateProfile(self, profile):
"""Create a new profile
@param profile: name of the profile
@param callback: called when the profile actually exists in database and memory
@return: a Deferred instance
"""
if self.storage.hasProfile(profile):
info(_('The profile name already exists'))
return defer.fail(Failure(exceptions.ConflictError))
if not self.host.trigger.point("ProfileCreation", profile):
return defer.fail(Failure(exceptions.CancelError))
return self.storage.createProfile(profile)
def asyncDeleteProfile(self, profile, force=False):
"""Delete an existing profile
@param profile: name of the profile
@param force: force the deletion even if the profile is connected.
To be used for direct calls only (not through the bridge).
@return: a Deferred instance
"""
if not self.storage.hasProfile(profile):
info(_('Trying to delete an unknown profile'))
return defer.fail(Failure(exceptions.ProfileUnknownError))
if not force and self.host.isConnected(profile):
info(_("Trying to delete a connected profile"))
return defer.fail(Failure(exceptions.ConnectedProfileError))
return self.storage.deleteProfile(profile)
def getProfileName(self, profile_key, return_profile_keys = False):
"""return profile according to profile_key
@param profile_key: profile name or key which can be
@ALL@ for all profiles
@DEFAULT@ for default profile
@param return_profile_keys: if True, return unmanaged profile keys (like "@ALL@"). This keys must be managed by the caller
@return: requested profile name or emptry string if it doesn't exist"""
if profile_key == '@DEFAULT@':
default = self.host.memory.memory_data.get('Profile_default')
if not default:
info(_('No default profile, returning first one')) # TODO: manage real default profile
try:
default = self.host.memory.memory_data['Profile_default'] = self.storage.getProfilesList()[0]
except IndexError:
info(_('No profile exist yet'))
return ""
return default # FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists
elif profile_key == '@NONE@':
raise exceptions.ProfileNotSetError
elif return_profile_keys and profile_key in ["@ALL@"]:
return profile_key # this value must be managed by the caller
if not self.storage.hasProfile(profile_key):
info(_('Trying to access an unknown profile'))
return "" # FIXME: raise exceptions.ProfileUnknownError here (must be well checked, this method is used in lot of places)
return profile_key
def __get_unique_node(self, parent, tag, name):
"""return node with given tag
@param parent: parent of nodes to check (e.g. documentElement)
@param tag: tag to check (e.g. "category")
@param name: name to check (e.g. "JID")
@return: node if it exist or None
"""
for node in parent.childNodes:
if node.nodeName == tag and node.getAttribute("name") == name:
#the node already exists
return node
#the node is new
return None
def updateParams(self, xml, security_limit=NO_SECURITY_LIMIT, app=''):
"""import xml in parameters, update if the param already exists
If security_limit is specified and greater than -1, the parameters
that have a security level greater than security_limit are skipped.
@param xml: parameters in xml form
@param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure
@param app: name of the frontend registering the parameters or empty value
"""
src_parent = minidom.parseString(xml.encode('utf-8')).documentElement
def pre_process_app_node(src_parent, security_limit, app):
"""Parameters that are registered from a frontend must be checked"""
to_remove = []
for type_node in src_parent.childNodes:
if type_node.nodeName != INDIVIDUAL:
to_remove.append(type_node) # accept individual parameters only
continue
for cat_node in type_node.childNodes:
if cat_node.nodeName != 'category':
to_remove.append(cat_node)
continue
to_remove_count = 0 # count the params to be removed from current category
for node in cat_node.childNodes:
if node.nodeName != "param" or not self.checkSecurityLimit(node, security_limit):
to_remove.append(node)
to_remove_count += 1
continue
node.setAttribute('app', app)
if len(cat_node.childNodes) == to_remove_count: # remove empty category
for dummy in xrange(0, to_remove_count):
to_remove.pop()
to_remove.append(cat_node)
for node in to_remove:
node.parentNode.removeChild(node)
def import_node(tgt_parent, src_parent):
for child in src_parent.childNodes:
if child.nodeName == '#text':
continue
node = self.__get_unique_node(tgt_parent, child.nodeName, child.getAttribute("name"))
if not node: # The node is new
tgt_parent.appendChild(child)
else:
if child.nodeName == "param":
# The child updates an existing parameter, we replace the node
tgt_parent.replaceChild(child, node)
else:
# the node already exists, we recurse 1 more level
import_node(node, child)
if app:
pre_process_app_node(src_parent, security_limit, app)
import_node(self.dom.documentElement, src_parent)
def paramsRegisterApp(self, xml, security_limit, app):
"""Register frontend's specific parameters
If security_limit is specified and greater than -1, the parameters
that have a security level greater than security_limit are skipped.
@param xml: XML definition of the parameters to be added
@param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure
@param app: name of the frontend registering the parameters
"""
if not app:
warning(_("Trying to register frontends parameters with no specified app: aborted"))
return
if not hasattr(self, "frontends_cache"):
self.frontends_cache = []
if app in self.frontends_cache:
debug(_("Trying to register twice frontends parameters for %(app)s: aborted" % {"app": app}))
return
self.frontends_cache.append(app)
self.updateParams(xml, security_limit, app)
debug("Frontends parameters registered for %(app)s" % {'app': app})
def __default_ok(self, value, name, category):
#FIXME: will not work with individual parameters
self.setParam(name, value, category)
def __default_ko(self, failure, name, category):
error(_("Can't determine default value for [%(category)s/%(name)s]: %(reason)s") % {'category': category, 'name': name, 'reason': str(failure.value)})
def setDefault(self, name, category, callback, errback=None):
"""Set default value of parameter
'default_cb' attibute of parameter must be set to 'yes'
@param name: name of the parameter
@param category: category of the parameter
@param callback: must return a string with the value (use deferred if needed)
@param errback: must manage the error with args failure, name, category
"""
#TODO: send signal param update if value changed
#TODO: manage individual paramaters
debug ("setDefault called for %(category)s/%(name)s" % {"category": category, "name": name})
node = self._getParamNode(name, category, '@ALL@')
if not node:
error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
return
if node[1].getAttribute('default_cb') == 'yes':
# 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,
# and we can still use it later e.g. to call a generic setDefault method
value = self._getParam(category, name, GENERAL)
if value is None: # no value set by the user: we have the default value
debug ("Default value to set, using callback")
d = defer.maybeDeferred(callback)
d.addCallback(self.__default_ok, name, category)
d.addErrback(errback or self.__default_ko, name, category)
def _getAttr(self, node, attr, value):
""" get attribute value
@param node: XML param node
@param attr: name of the attribute to get (e.g.: 'value' or 'type')
@param value: user defined value"""
if attr == 'value':
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
if node.getAttribute('type') == 'bool':
return value_to_use.lower() not in ('false', '0', 'no')
return value_to_use
return node.getAttribute(attr)
def __type_to_string(self, result):
""" convert result to string, according to its type """
if isinstance(result, bool):
return "true" if result else "false"
return result
def getStringParamA(self, name, category, attr="value", profile_key="@NONE@"):
""" Same as getParamA but for bridge: convert non string value to string """
return self.__type_to_string(self.getParamA(name, category, attr, profile_key))
def getParamA(self, name, category, attr="value", profile_key="@NONE@"):
"""Helper method to get a specific attribute
@param name: name of the parameter
@param category: category of the parameter
@param attr: name of the attribute (default: "value")
@param profile: owner of the param (@ALL@ for everyone)
@return: attribute"""
#FIXME: looks really dirty and buggy, need to be reviewed/refactored
node = self._getParamNode(name, category)
if not node:
error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
raise exceptions.NotFound
if node[0] == GENERAL:
value = self._getParam(category, name, GENERAL)
return self._getAttr(node[1], attr, value)
assert node[0] == INDIVIDUAL
profile = self.getProfileName(profile_key)
if not profile:
error(_('Requesting a param for an non-existant profile'))
raise exceptions.ProfileUnknownError
if profile not in self.params:
error(_('Requesting synchronous param for not connected profile'))
raise exceptions.NotConnectedProfileError(profile)
if attr == "value":
value = self._getParam(category, name, profile=profile)
return self._getAttr(node[1], attr, value)
def asyncGetStringParamA(self, name, category, attr="value", security_limit=NO_SECURITY_LIMIT, profile_key="@NONE@"):
d = self.asyncGetParamA(name, category, attr, security_limit, profile_key)
d.addCallback(self.__type_to_string)
return d
def asyncGetParamA(self, name, category, attr="value", security_limit=NO_SECURITY_LIMIT, profile_key="@NONE@"):
"""Helper method to get a specific attribute
@param name: name of the parameter
@param category: category of the parameter
@param attr: name of the attribute (default: "value")
@param profile: owner of the param (@ALL@ for everyone)"""
node = self._getParamNode(name, category)
if not node:
error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
return None
if not self.checkSecurityLimit(node[1], security_limit):
warning(_("Trying to get parameter '%(param)s' in category '%(cat)s' without authorization!!!"
% {'param': name, 'cat': category}))
return None
if node[0] == GENERAL:
value = self._getParam(category, name, GENERAL)
return defer.succeed(self._getAttr(node[1], attr, value))
assert node[0] == INDIVIDUAL
profile = self.getProfileName(profile_key)
if not profile:
error(_('Requesting a param for a non-existant profile'))
return defer.fail()
if attr != "value":
return defer.succeed(node[1].getAttribute(attr))
try:
value = self._getParam(category, name, profile=profile)
return defer.succeed(self._getAttr(node[1], attr, value))
except exceptions.ProfileNotInCacheError:
#We have to ask data to the storage manager
d = self.storage.getIndParam(category, name, profile)
return d.addCallback(lambda value: self._getAttr(node[1], attr, value))
def _getParam(self, category, name, type_=INDIVIDUAL, cache=None, profile="@NONE@"):
"""Return the param, or None if it doesn't exist
@param category: param category
@param name: param name
@param type_: GENERAL or INDIVIDUAL
@param cache: temporary cache, to use when profile is not logged
@param profile: the profile name (not profile key, i.e. name and not something like @DEFAULT@)
@return: param value or None if it doesn't exist
"""
if type_ == GENERAL:
if (category, name) in self.params_gen:
return self.params_gen[(category, name)]
return None # This general param has the default value
assert (type_ == INDIVIDUAL)
if profile == "@NONE@":
raise exceptions.ProfileNotSetError
if profile in self.params:
cache = self.params[profile] # if profile is in main cache, we use it,
# ignoring the temporary cache
elif cache is None: # else we use the temporary cache if it exists, or raise an exception
raise exceptions.ProfileNotInCacheError
if (category, name) not in cache:
return None
return cache[(category, name)]
def __constructProfileXml(self, security_limit, app, profile):
"""Construct xml for asked profile, filling values when needed
/!\ as noticed in doc, don't forget to unlink the minidom.Document
@param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
Otherwise sole the params which have a security level defined *and*
lower or equal to the specified value are returned.
@param app: name of the frontend requesting the parameters, or '' to get all parameters
@param profile: profile name (not key !)
@return: a deferred that fire a minidom.Document of the profile xml (cf warning above)
"""
def checkNode(node):
"""Check the node against security_limit and app"""
return self.checkSecurityLimit(node, security_limit) and self.checkApp(node, app)
def constructProfile(ignore, profile_cache):
# init the result document
prof_xml = minidom.parseString('<params/>')
cache = {}
for type_node in self.dom.documentElement.childNodes:
if type_node.nodeName != GENERAL and type_node.nodeName != INDIVIDUAL:
continue
# we use all params, general and individual
for cat_node in type_node.childNodes:
if cat_node.nodeName != 'category':
continue
category = cat_node.getAttribute('name')
dest_params = {} # result (merged) params for category
if category not in cache:
# we make a copy for the new xml
cache[category] = dest_cat = cat_node.cloneNode(True)
for node in dest_cat.childNodes:
if node.nodeName != "param":
continue
if not checkNode(node):
dest_cat.removeChild(node)
continue
dest_params[node.getAttribute('name')] = node
new_node = True
else:
# It's not a new node, we use the previously cloned one
dest_cat = cache[category]
new_node = False
params = cat_node.getElementsByTagName("param")
for param_node in params:
# we have to merge new params (we are parsing individual parameters, we have to add them
# to the previously parsed general ones)
name = param_node.getAttribute('name')
if not checkNode(param_node):
continue
if name not in dest_params:
# this is reached when a previous category exists
dest_params[name] = param_node.cloneNode(True)
dest_cat.appendChild(dest_params[name])
profile_value = self._getParam(category,
name, type_node.nodeName,
cache=profile_cache, profile=profile)
if profile_value is not None:
# there is a value for this profile, we must change the default
dest_params[name].setAttribute('value', profile_value)
if new_node:
prof_xml.documentElement.appendChild(dest_cat)
to_remove = []
for cat_node in prof_xml.documentElement.childNodes:
# we remove empty categories
if cat_node.getElementsByTagName("param").length == 0:
to_remove.append(cat_node)
for node in to_remove:
prof_xml.documentElement.removeChild(node)
return prof_xml
if profile in self.params:
d = defer.succeed(None)
profile_cache = self.params[profile]
else:
#profile is not in cache, we load values in a short time cache
profile_cache = {}
d = self.loadIndParams(profile, profile_cache)
return d.addCallback(constructProfile, profile_cache)
def getParamsUI(self, security_limit, app, profile_key):
"""
@param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
Otherwise sole the params which have a security level defined *and*
lower or equal to the specified value are returned.
@param app: name of the frontend requesting the parameters, or '' to get all parameters
@param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile.
@return: a SàT XMLUI for parameters
"""
profile = self.getProfileName(profile_key)
if not profile:
error(_("Asking params for inexistant profile"))
return ""
d = self.getParams(security_limit, app, profile)
return d.addCallback(lambda param_xml: paramsXML2XMLUI(param_xml))
def getParams(self, security_limit, app, profile_key):
"""Construct xml for asked profile, take params xml as skeleton
@param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
Otherwise sole the params which have a security level defined *and*
lower or equal to the specified value are returned.
@param app: name of the frontend requesting the parameters, or '' to get all parameters
@param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile.
@return: XML of parameters
"""
profile = self.getProfileName(profile_key)
if not profile:
error(_("Asking params for inexistant profile"))
return defer.succeed("")
def returnXML(prof_xml):
return_xml = prof_xml.toxml()
prof_xml.unlink()
return '\n'.join((line for line in return_xml.split('\n') if line))
return self.__constructProfileXml(security_limit, app, profile).addCallback(returnXML)
def getParamsForCategory(self, category, security_limit, app, profile_key):
"""
@param category: the desired category
@param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
Otherwise sole the params which have a security level defined *and*
lower or equal to the specified value are returned.
@param app: name of the frontend requesting the parameters, or '' to get all parameters
@param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile.
@return: node's xml for selected category
"""
#TODO: manage category of general type (without existant profile)
profile = self.getProfileName(profile_key)
if not profile:
error(_("Asking params for inexistant profile"))
return ""
def returnCategoryXml(prof_xml):
for node in prof_xml.getElementsByTagName("category"):
if node.nodeName == "category" and node.getAttribute("name") == category:
result = node.toxml()
prof_xml.unlink()
return result
prof_xml.unlink()
return "<category />"
d = self.__constructProfileXml(security_limit, app, profile)
return d.addCallback(returnCategoryXml)
def _getParamNode(self, name, category, type_="@ALL@"): # FIXME: is type_ useful ?
"""Return a node from the param_xml
@param name: name of the node
@param category: category of the node
@type_: keyword for search:
@ALL@ search everywhere
@GENERAL@ only search in general type
@INDIVIDUAL@ only search in individual type
@return: a tuple with the node type and the the node, or None if not found"""
for type_node in self.dom.documentElement.childNodes:
if (((type_ == "@ALL@" or type_ == "@GENERAL@") and type_node.nodeName == GENERAL)
or ((type_ == "@ALL@" or type_ == "@INDIVIDUAL@") and type_node.nodeName == INDIVIDUAL)):
for node in type_node.getElementsByTagName('category'):
if node.getAttribute("name") == category:
params = node.getElementsByTagName("param")
for param in params:
if param.getAttribute("name") == name:
return (type_node.nodeName, param)
return None
def getParamsCategories(self):
"""return the categories availables"""
categories = []
for cat in self.dom.getElementsByTagName("category"):
name = cat.getAttribute("name")
if name not in categories:
categories.append(cat.getAttribute("name"))
return categories
def setParam(self, name, value, category, security_limit=NO_SECURITY_LIMIT, profile_key='@NONE@'):
"""Set a parameter, return None if the parameter is not in param xml"""
#TODO: use different behaviour depending of the data type (e.g. password encrypted)
if profile_key != "@NONE@":
profile = self.getProfileName(profile_key)
if not profile:
error(_('Trying to set parameter for an unknown profile'))
return # TODO: throw an error
node = self._getParamNode(name, category, '@ALL@')
if not node:
error(_('Requesting an unknown parameter (%(category)s/%(name)s)')
% {'category': category, 'name': name})
return
if not self.checkSecurityLimit(node[1], security_limit):
warning(_("Trying to set parameter '%(param)s' in category '%(cat)s' without authorization!!!"
% {'param': name, 'cat': category}))
return
if node[0] == GENERAL:
self.params_gen[(category, name)] = value
self.storage.setGenParam(category, name, value)
for profile in self.storage.getProfilesList():
if self.host.isConnected(profile):
self.host.bridge.paramUpdate(name, value, category, profile)
self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile)
return
assert (node[0] == INDIVIDUAL)
assert (profile_key != "@NONE@")
type_ = node[1].getAttribute("type")
if type_ == "button":
print "clique", node.toxml()
else:
if self.host.isConnected(profile): # key can not exists if profile is not connected
self.params[profile][(category, name)] = value
self.host.bridge.paramUpdate(name, value, category, profile)
self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile)
self.storage.setIndParam(category, name, value, profile)
def checkSecurityLimit(self, node, security_limit):
"""Check the given node against the given security limit.
The value NO_SECURITY_LIMIT (-1) means that everything is allowed.
@return: True if this node can be accessed with the given security limit.
"""
if security_limit < 0:
return True
if node.hasAttribute("security"):
if int(node.getAttribute("security")) <= security_limit:
return True
return False
def checkApp(self, node, app):
"""Check the given node against the given app.
@param node: parameter node
@param app: name of the frontend requesting the parameters, or '' to get all parameters
@return: True if this node concerns the given app.
"""
if not app or not node.hasAttribute("app"):
return True
return node.getAttribute("app") == app
class Memory(object):
"""This class manage all persistent informations"""
@ -770,7 +118,7 @@ class Memory(object):
self.server_features = {} # used to store discovery's informations
self.server_identities = {}
self.config = self.parseMainConf()
host.setConst('savefile_database', SAVEFILE_DATABASE)
host.setConst('savefile_database', C.SAVEFILE_DATABASE)
database_file = os.path.expanduser(self.getConfig('', 'local_dir') +
self.host.getConst('savefile_database'))
self.storage = SqliteStorage(database_file, host.__version__)
@ -1131,31 +479,31 @@ class Memory(object):
def getParamA(self, name, category, attr="value", profile_key='@NONE@'):
return self.params.getParamA(name, category, attr, profile_key)
def asyncGetParamA(self, name, category, attr="value", security_limit=NO_SECURITY_LIMIT, profile_key='@NONE@'):
def asyncGetParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key='@NONE@'):
return self.params.asyncGetParamA(name, category, attr, security_limit, profile_key)
def asyncGetStringParamA(self, name, category, attr="value", security_limit=NO_SECURITY_LIMIT, profile_key='@NONE@'):
def asyncGetStringParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key='@NONE@'):
return self.params.asyncGetStringParamA(name, category, attr, security_limit, profile_key)
def getParamsUI(self, security_limit=NO_SECURITY_LIMIT, app='', profile_key='@NONE@'):
def getParamsUI(self, security_limit=C.NO_SECURITY_LIMIT, app='', profile_key='@NONE@'):
return self.params.getParamsUI(security_limit, app, profile_key)
def getParams(self, security_limit=NO_SECURITY_LIMIT, app='', profile_key='@NONE@'):
def getParams(self, security_limit=C.NO_SECURITY_LIMIT, app='', profile_key='@NONE@'):
return self.params.getParams(security_limit, app, profile_key)
def getParamsForCategory(self, category, security_limit=NO_SECURITY_LIMIT, app='', profile_key='@NONE@'):
def getParamsForCategory(self, category, security_limit=C.NO_SECURITY_LIMIT, app='', profile_key='@NONE@'):
return self.params.getParamsForCategory(category, security_limit, app, profile_key)
def getParamsCategories(self):
return self.params.getParamsCategories()
def setParam(self, name, value, category, security_limit=NO_SECURITY_LIMIT, profile_key='@NONE@'):
def setParam(self, name, value, category, security_limit=C.NO_SECURITY_LIMIT, profile_key='@NONE@'):
return self.params.setParam(name, value, category, security_limit, profile_key)
def updateParams(self, xml):
return self.params.updateParams(xml)
def paramsRegisterApp(self, xml, security_limit=NO_SECURITY_LIMIT, app=''):
def paramsRegisterApp(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=''):
return self.params.paramsRegisterApp(xml, security_limit, app)
def setDefault(self, name, category, callback, errback=None):

673
src/memory/params.py Normal file
View file

@ -0,0 +1,673 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# SAT: a jabber client
# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sat.core.i18n import _
from sat.core import exceptions
from sat.core.constants import Const as C
from xml.dom import minidom
from logging import debug, info, warning, error
from twisted.internet import defer
from twisted.python.failure import Failure
from sat.tools.xml_tools import paramsXML2XMLUI
class Params(object):
"""This class manage parameters with xml"""
### TODO: add desciption in params
#TODO: move Watched in a plugin
#TODO: when priority is changed, a new presence stanza must be emitted
#TODO: int type (Priority should be int instead of string)
default_xml = u"""
<params>
<general>
</general>
<individual>
<category name="Connection" label="%(category_connection)s">
<param name="JabberID" value="name@example.org/SàT" type="string" />
<param name="Password" value="" type="password" />
<param name="Priority" value="50" type="string" />
<param name="Server" value="example.org" type="string" />
<param name="Port" value="5222" type="string" />
<param name="NewAccount" label="%(label_NewAccount)s" type="button" callback_id="registerNewAccount"/>
<param name="autoconnect" label="%(label_autoconnect)s" value="true" type="bool" />
<param name="autodisconnect" label="%(label_autodisconnect)s" value="false" type="bool" />
</category>
<category name="Misc" label="%(category_misc)s">
<param name="Watched" value="test@Jabber.goffi.int" type="string" />
</category>
</individual>
</params>
""" % {
'category_connection': _("Connection"),
'label_NewAccount': _("Register new account"),
'label_autoconnect': _('Connect on frontend startup'),
'label_autodisconnect': _('Disconnect on frontend closure'),
'category_misc': _("Misc")
}
def load_default_params(self):
self.dom = minidom.parseString(Params.default_xml.encode('utf-8'))
def _mergeParams(self, source_node, dest_node):
"""Look for every node in source_node and recursively copy them to dest if they don't exists"""
def getNodesMap(children):
ret = {}
for child in children:
if child.nodeType == child.ELEMENT_NODE:
ret[(child.tagName, child.getAttribute('name'))] = child
return ret
source_map = getNodesMap(source_node.childNodes)
dest_map = getNodesMap(dest_node.childNodes)
source_set = set(source_map.keys())
dest_set = set(dest_map.keys())
to_add = source_set.difference(dest_set)
for node_key in to_add:
dest_node.appendChild(source_map[node_key].cloneNode(True))
to_recurse = source_set - to_add
for node_key in to_recurse:
self._mergeParams(source_map[node_key], dest_map[node_key])
def load_xml(self, xml_file):
"""Load parameters template from file"""
self.dom = minidom.parse(xml_file)
default_dom = minidom.parseString(Params.default_xml.encode('utf-8'))
self._mergeParams(default_dom.documentElement, self.dom.documentElement)
def loadGenParams(self):
"""Load general parameters data from storage
@return: deferred triggered once params are loaded"""
return self.storage.loadGenParams(self.params_gen)
def loadIndParams(self, profile, cache=None):
"""Load individual parameters
set self.params cache or a temporary cache
@param profile: profile to load (*must exist*)
@param cache: if not None, will be used to store the value, as a short time cache
@return: deferred triggered once params are loaded"""
if cache is None:
self.params[profile] = {}
return self.storage.loadIndParams(self.params[profile] if cache is None else cache, profile)
def purgeProfile(self, profile):
"""Remove cache data of a profile
@param profile: %(doc_profile)s"""
try:
del self.params[profile]
except KeyError:
error(_("Trying to purge cache of a profile not in memory: [%s]") % profile)
def save_xml(self, filename):
"""Save parameters template to xml file"""
with open(filename, 'wb') as xml_file:
xml_file.write(self.dom.toxml('utf-8'))
def __init__(self, host, storage):
debug("Parameters init")
self.host = host
self.storage = storage
self.default_profile = None
self.params = {}
self.params_gen = {}
host.registerCallback(host.registerNewAccountCB, with_data=True, force_id="registerNewAccount")
def asyncCreateProfile(self, profile):
"""Create a new profile
@param profile: name of the profile
@param callback: called when the profile actually exists in database and memory
@return: a Deferred instance
"""
if self.storage.hasProfile(profile):
info(_('The profile name already exists'))
return defer.fail(Failure(exceptions.ConflictError))
if not self.host.trigger.point("ProfileCreation", profile):
return defer.fail(Failure(exceptions.CancelError))
return self.storage.createProfile(profile)
def asyncDeleteProfile(self, profile, force=False):
"""Delete an existing profile
@param profile: name of the profile
@param force: force the deletion even if the profile is connected.
To be used for direct calls only (not through the bridge).
@return: a Deferred instance
"""
if not self.storage.hasProfile(profile):
info(_('Trying to delete an unknown profile'))
return defer.fail(Failure(exceptions.ProfileUnknownError))
if not force and self.host.isConnected(profile):
info(_("Trying to delete a connected profile"))
return defer.fail(Failure(exceptions.ConnectedProfileError))
return self.storage.deleteProfile(profile)
def getProfileName(self, profile_key, return_profile_keys = False):
"""return profile according to profile_key
@param profile_key: profile name or key which can be
@ALL@ for all profiles
@DEFAULT@ for default profile
@param return_profile_keys: if True, return unmanaged profile keys (like "@ALL@"). This keys must be managed by the caller
@return: requested profile name or emptry string if it doesn't exist"""
if profile_key == '@DEFAULT@':
default = self.host.memory.memory_data.get('Profile_default')
if not default:
info(_('No default profile, returning first one')) # TODO: manage real default profile
try:
default = self.host.memory.memory_data['Profile_default'] = self.storage.getProfilesList()[0]
except IndexError:
info(_('No profile exist yet'))
return ""
return default # FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists
elif profile_key == '@NONE@':
raise exceptions.ProfileNotSetError
elif return_profile_keys and profile_key in ["@ALL@"]:
return profile_key # this value must be managed by the caller
if not self.storage.hasProfile(profile_key):
info(_('Trying to access an unknown profile'))
return "" # FIXME: raise exceptions.ProfileUnknownError here (must be well checked, this method is used in lot of places)
return profile_key
def __get_unique_node(self, parent, tag, name):
"""return node with given tag
@param parent: parent of nodes to check (e.g. documentElement)
@param tag: tag to check (e.g. "category")
@param name: name to check (e.g. "JID")
@return: node if it exist or None
"""
for node in parent.childNodes:
if node.nodeName == tag and node.getAttribute("name") == name:
#the node already exists
return node
#the node is new
return None
def updateParams(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=''):
"""import xml in parameters, update if the param already exists
If security_limit is specified and greater than -1, the parameters
that have a security level greater than security_limit are skipped.
@param xml: parameters in xml form
@param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure
@param app: name of the frontend registering the parameters or empty value
"""
src_parent = minidom.parseString(xml.encode('utf-8')).documentElement
def pre_process_app_node(src_parent, security_limit, app):
"""Parameters that are registered from a frontend must be checked"""
to_remove = []
for type_node in src_parent.childNodes:
if type_node.nodeName != C.INDIVIDUAL:
to_remove.append(type_node) # accept individual parameters only
continue
for cat_node in type_node.childNodes:
if cat_node.nodeName != 'category':
to_remove.append(cat_node)
continue
to_remove_count = 0 # count the params to be removed from current category
for node in cat_node.childNodes:
if node.nodeName != "param" or not self.checkSecurityLimit(node, security_limit):
to_remove.append(node)
to_remove_count += 1
continue
node.setAttribute('app', app)
if len(cat_node.childNodes) == to_remove_count: # remove empty category
for dummy in xrange(0, to_remove_count):
to_remove.pop()
to_remove.append(cat_node)
for node in to_remove:
node.parentNode.removeChild(node)
def import_node(tgt_parent, src_parent):
for child in src_parent.childNodes:
if child.nodeName == '#text':
continue
node = self.__get_unique_node(tgt_parent, child.nodeName, child.getAttribute("name"))
if not node: # The node is new
tgt_parent.appendChild(child)
else:
if child.nodeName == "param":
# The child updates an existing parameter, we replace the node
tgt_parent.replaceChild(child, node)
else:
# the node already exists, we recurse 1 more level
import_node(node, child)
if app:
pre_process_app_node(src_parent, security_limit, app)
import_node(self.dom.documentElement, src_parent)
def paramsRegisterApp(self, xml, security_limit, app):
"""Register frontend's specific parameters
If security_limit is specified and greater than -1, the parameters
that have a security level greater than security_limit are skipped.
@param xml: XML definition of the parameters to be added
@param security_limit: -1 means no security, 0 is the maximum security then the higher the less secure
@param app: name of the frontend registering the parameters
"""
if not app:
warning(_("Trying to register frontends parameters with no specified app: aborted"))
return
if not hasattr(self, "frontends_cache"):
self.frontends_cache = []
if app in self.frontends_cache:
debug(_("Trying to register twice frontends parameters for %(app)s: aborted" % {"app": app}))
return
self.frontends_cache.append(app)
self.updateParams(xml, security_limit, app)
debug("Frontends parameters registered for %(app)s" % {'app': app})
def __default_ok(self, value, name, category):
#FIXME: will not work with individual parameters
self.setParam(name, value, category)
def __default_ko(self, failure, name, category):
error(_("Can't determine default value for [%(category)s/%(name)s]: %(reason)s") % {'category': category, 'name': name, 'reason': str(failure.value)})
def setDefault(self, name, category, callback, errback=None):
"""Set default value of parameter
'default_cb' attibute of parameter must be set to 'yes'
@param name: name of the parameter
@param category: category of the parameter
@param callback: must return a string with the value (use deferred if needed)
@param errback: must manage the error with args failure, name, category
"""
#TODO: send signal param update if value changed
#TODO: manage individual paramaters
debug ("setDefault called for %(category)s/%(name)s" % {"category": category, "name": name})
node = self._getParamNode(name, category, '@ALL@')
if not node:
error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
return
if node[1].getAttribute('default_cb') == 'yes':
# 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,
# and we can still use it later e.g. to call a generic setDefault method
value = self._getParam(category, name, C.GENERAL)
if value is None: # no value set by the user: we have the default value
debug ("Default value to set, using callback")
d = defer.maybeDeferred(callback)
d.addCallback(self.__default_ok, name, category)
d.addErrback(errback or self.__default_ko, name, category)
def _getAttr(self, node, attr, value):
""" get attribute value
@param node: XML param node
@param attr: name of the attribute to get (e.g.: 'value' or 'type')
@param value: user defined value"""
if attr == 'value':
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
if node.getAttribute('type') == 'bool':
return value_to_use.lower() not in ('false', '0', 'no')
return value_to_use
return node.getAttribute(attr)
def __type_to_string(self, result):
""" convert result to string, according to its type """
if isinstance(result, bool):
return "true" if result else "false"
return result
def getStringParamA(self, name, category, attr="value", profile_key="@NONE@"):
""" Same as getParamA but for bridge: convert non string value to string """
return self.__type_to_string(self.getParamA(name, category, attr, profile_key))
def getParamA(self, name, category, attr="value", profile_key="@NONE@"):
"""Helper method to get a specific attribute
@param name: name of the parameter
@param category: category of the parameter
@param attr: name of the attribute (default: "value")
@param profile: owner of the param (@ALL@ for everyone)
@return: attribute"""
#FIXME: looks really dirty and buggy, need to be reviewed/refactored
node = self._getParamNode(name, category)
if not node:
error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
raise exceptions.NotFound
if node[0] == C.GENERAL:
value = self._getParam(category, name, C.GENERAL)
return self._getAttr(node[1], attr, value)
assert node[0] == C.INDIVIDUAL
profile = self.getProfileName(profile_key)
if not profile:
error(_('Requesting a param for an non-existant profile'))
raise exceptions.ProfileUnknownError
if profile not in self.params:
error(_('Requesting synchronous param for not connected profile'))
raise exceptions.NotConnectedProfileError(profile)
if attr == "value":
value = self._getParam(category, name, profile=profile)
return self._getAttr(node[1], attr, value)
def asyncGetStringParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key="@NONE@"):
d = self.asyncGetParamA(name, category, attr, security_limit, profile_key)
d.addCallback(self.__type_to_string)
return d
def asyncGetParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key="@NONE@"):
"""Helper method to get a specific attribute
@param name: name of the parameter
@param category: category of the parameter
@param attr: name of the attribute (default: "value")
@param profile: owner of the param (@ALL@ for everyone)"""
node = self._getParamNode(name, category)
if not node:
error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
return None
if not self.checkSecurityLimit(node[1], security_limit):
warning(_("Trying to get parameter '%(param)s' in category '%(cat)s' without authorization!!!"
% {'param': name, 'cat': category}))
return None
if node[0] == C.GENERAL:
value = self._getParam(category, name, C.GENERAL)
return defer.succeed(self._getAttr(node[1], attr, value))
assert node[0] == C.INDIVIDUAL
profile = self.getProfileName(profile_key)
if not profile:
error(_('Requesting a param for a non-existant profile'))
return defer.fail()
if attr != "value":
return defer.succeed(node[1].getAttribute(attr))
try:
value = self._getParam(category, name, profile=profile)
return defer.succeed(self._getAttr(node[1], attr, value))
except exceptions.ProfileNotInCacheError:
#We have to ask data to the storage manager
d = self.storage.getIndParam(category, name, profile)
return d.addCallback(lambda value: self._getAttr(node[1], attr, value))
def _getParam(self, category, name, type_=C.INDIVIDUAL, cache=None, profile="@NONE@"):
"""Return the param, or None if it doesn't exist
@param category: param category
@param name: param name
@param type_: GENERAL or INDIVIDUAL
@param cache: temporary cache, to use when profile is not logged
@param profile: the profile name (not profile key, i.e. name and not something like @DEFAULT@)
@return: param value or None if it doesn't exist
"""
if type_ == C.GENERAL:
if (category, name) in self.params_gen:
return self.params_gen[(category, name)]
return None # This general param has the default value
assert (type_ == C.INDIVIDUAL)
if profile == "@NONE@":
raise exceptions.ProfileNotSetError
if profile in self.params:
cache = self.params[profile] # if profile is in main cache, we use it,
# ignoring the temporary cache
elif cache is None: # else we use the temporary cache if it exists, or raise an exception
raise exceptions.ProfileNotInCacheError
if (category, name) not in cache:
return None
return cache[(category, name)]
def __constructProfileXml(self, security_limit, app, profile):
"""Construct xml for asked profile, filling values when needed
/!\ as noticed in doc, don't forget to unlink the minidom.Document
@param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
Otherwise sole the params which have a security level defined *and*
lower or equal to the specified value are returned.
@param app: name of the frontend requesting the parameters, or '' to get all parameters
@param profile: profile name (not key !)
@return: a deferred that fire a minidom.Document of the profile xml (cf warning above)
"""
def checkNode(node):
"""Check the node against security_limit and app"""
return self.checkSecurityLimit(node, security_limit) and self.checkApp(node, app)
def constructProfile(ignore, profile_cache):
# init the result document
prof_xml = minidom.parseString('<params/>')
cache = {}
for type_node in self.dom.documentElement.childNodes:
if type_node.nodeName != C.GENERAL and type_node.nodeName != C.INDIVIDUAL:
continue
# we use all params, general and individual
for cat_node in type_node.childNodes:
if cat_node.nodeName != 'category':
continue
category = cat_node.getAttribute('name')
dest_params = {} # result (merged) params for category
if category not in cache:
# we make a copy for the new xml
cache[category] = dest_cat = cat_node.cloneNode(True)
for node in dest_cat.childNodes:
if node.nodeName != "param":
continue
if not checkNode(node):
dest_cat.removeChild(node)
continue
dest_params[node.getAttribute('name')] = node
new_node = True
else:
# It's not a new node, we use the previously cloned one
dest_cat = cache[category]
new_node = False
params = cat_node.getElementsByTagName("param")
for param_node in params:
# we have to merge new params (we are parsing individual parameters, we have to add them
# to the previously parsed general ones)
name = param_node.getAttribute('name')
if not checkNode(param_node):
continue
if name not in dest_params:
# this is reached when a previous category exists
dest_params[name] = param_node.cloneNode(True)
dest_cat.appendChild(dest_params[name])
profile_value = self._getParam(category,
name, type_node.nodeName,
cache=profile_cache, profile=profile)
if profile_value is not None:
# there is a value for this profile, we must change the default
dest_params[name].setAttribute('value', profile_value)
if new_node:
prof_xml.documentElement.appendChild(dest_cat)
to_remove = []
for cat_node in prof_xml.documentElement.childNodes:
# we remove empty categories
if cat_node.getElementsByTagName("param").length == 0:
to_remove.append(cat_node)
for node in to_remove:
prof_xml.documentElement.removeChild(node)
return prof_xml
if profile in self.params:
d = defer.succeed(None)
profile_cache = self.params[profile]
else:
#profile is not in cache, we load values in a short time cache
profile_cache = {}
d = self.loadIndParams(profile, profile_cache)
return d.addCallback(constructProfile, profile_cache)
def getParamsUI(self, security_limit, app, profile_key):
"""
@param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
Otherwise sole the params which have a security level defined *and*
lower or equal to the specified value are returned.
@param app: name of the frontend requesting the parameters, or '' to get all parameters
@param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile.
@return: a SàT XMLUI for parameters
"""
profile = self.getProfileName(profile_key)
if not profile:
error(_("Asking params for inexistant profile"))
return ""
d = self.getParams(security_limit, app, profile)
return d.addCallback(lambda param_xml: paramsXML2XMLUI(param_xml))
def getParams(self, security_limit, app, profile_key):
"""Construct xml for asked profile, take params xml as skeleton
@param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
Otherwise sole the params which have a security level defined *and*
lower or equal to the specified value are returned.
@param app: name of the frontend requesting the parameters, or '' to get all parameters
@param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile.
@return: XML of parameters
"""
profile = self.getProfileName(profile_key)
if not profile:
error(_("Asking params for inexistant profile"))
return defer.succeed("")
def returnXML(prof_xml):
return_xml = prof_xml.toxml()
prof_xml.unlink()
return '\n'.join((line for line in return_xml.split('\n') if line))
return self.__constructProfileXml(security_limit, app, profile).addCallback(returnXML)
def getParamsForCategory(self, category, security_limit, app, profile_key):
"""
@param category: the desired category
@param security_limit: NO_SECURITY_LIMIT (-1) to return all the params.
Otherwise sole the params which have a security level defined *and*
lower or equal to the specified value are returned.
@param app: name of the frontend requesting the parameters, or '' to get all parameters
@param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile.
@return: node's xml for selected category
"""
#TODO: manage category of general type (without existant profile)
profile = self.getProfileName(profile_key)
if not profile:
error(_("Asking params for inexistant profile"))
return ""
def returnCategoryXml(prof_xml):
for node in prof_xml.getElementsByTagName("category"):
if node.nodeName == "category" and node.getAttribute("name") == category:
result = node.toxml()
prof_xml.unlink()
return result
prof_xml.unlink()
return "<category />"
d = self.__constructProfileXml(security_limit, app, profile)
return d.addCallback(returnCategoryXml)
def _getParamNode(self, name, category, type_="@ALL@"): # FIXME: is type_ useful ?
"""Return a node from the param_xml
@param name: name of the node
@param category: category of the node
@type_: keyword for search:
@ALL@ search everywhere
@GENERAL@ only search in general type
@INDIVIDUAL@ only search in individual type
@return: a tuple with the node type and the the node, or None if not found"""
for type_node in self.dom.documentElement.childNodes:
if (((type_ == "@ALL@" or type_ == "@GENERAL@") and type_node.nodeName == C.GENERAL)
or ((type_ == "@ALL@" or type_ == "@INDIVIDUAL@") and type_node.nodeName == C.INDIVIDUAL)):
for node in type_node.getElementsByTagName('category'):
if node.getAttribute("name") == category:
params = node.getElementsByTagName("param")
for param in params:
if param.getAttribute("name") == name:
return (type_node.nodeName, param)
return None
def getParamsCategories(self):
"""return the categories availables"""
categories = []
for cat in self.dom.getElementsByTagName("category"):
name = cat.getAttribute("name")
if name not in categories:
categories.append(cat.getAttribute("name"))
return categories
def setParam(self, name, value, category, security_limit=C.NO_SECURITY_LIMIT, profile_key='@NONE@'):
"""Set a parameter, return None if the parameter is not in param xml"""
#TODO: use different behaviour depending of the data type (e.g. password encrypted)
if profile_key != "@NONE@":
profile = self.getProfileName(profile_key)
if not profile:
error(_('Trying to set parameter for an unknown profile'))
return # TODO: throw an error
node = self._getParamNode(name, category, '@ALL@')
if not node:
error(_('Requesting an unknown parameter (%(category)s/%(name)s)')
% {'category': category, 'name': name})
return
if not self.checkSecurityLimit(node[1], security_limit):
warning(_("Trying to set parameter '%(param)s' in category '%(cat)s' without authorization!!!"
% {'param': name, 'cat': category}))
return
if node[0] == C.GENERAL:
self.params_gen[(category, name)] = value
self.storage.setGenParam(category, name, value)
for profile in self.storage.getProfilesList():
if self.host.isConnected(profile):
self.host.bridge.paramUpdate(name, value, category, profile)
self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile)
return
assert (node[0] == C.INDIVIDUAL)
assert (profile_key != "@NONE@")
type_ = node[1].getAttribute("type")
if type_ == "button":
print "clique", node.toxml()
else:
if self.host.isConnected(profile): # key can not exists if profile is not connected
self.params[profile][(category, name)] = value
self.host.bridge.paramUpdate(name, value, category, profile)
self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile)
self.storage.setIndParam(category, name, value, profile)
def checkSecurityLimit(self, node, security_limit):
"""Check the given node against the given security limit.
The value NO_SECURITY_LIMIT (-1) means that everything is allowed.
@return: True if this node can be accessed with the given security limit.
"""
if security_limit < 0:
return True
if node.hasAttribute("security"):
if int(node.getAttribute("security")) <= security_limit:
return True
return False
def checkApp(self, node, app):
"""Check the given node against the given app.
@param node: parameter node
@param app: name of the frontend requesting the parameters, or '' to get all parameters
@return: True if this node concerns the given app.
"""
if not app or not node.hasAttribute("app"):
return True
return node.getAttribute("app") == app