Move disco_get_info and disco_get_items to AccountManager

discovery get_info and get_items handler now use a generic
method "apply_behavior" to determine receiver (main component,
account type node or account).
AccountManager split previous handler in a more testable way
(test returned objects instead of XML).

darcs-hash:20070327163756-86b55-3932d4b1f59fb20d6aa8e096cdf06049095a4c2b.gz
This commit is contained in:
David Rousselie
2007-03-27 18:37:56 +02:00
parent 0fad164e57
commit 516bb75a77
3 changed files with 199 additions and 89 deletions

View File

@@ -90,12 +90,11 @@ class JCLComponent(Component, object):
self.name = "Jabber Component Library generic component"
self.spool_dir = "."
self.db_connection_str = db_connection_str
self.account_classes = [Account]
self.version = VERSION
self.accounts = []
self.time_unit = 60
self.queue = Queue(100)
self.account_manager = AccountManager(self)
self.__logger = logging.getLogger("jcl.jabber.JCLComponent")
self.lang = lang
self.running = False
@@ -104,6 +103,9 @@ class JCLComponent(Component, object):
signal.signal(signal.SIGINT, self.signal_handler)
signal.signal(signal.SIGTERM, self.signal_handler)
# TODO : delete
self.account_classes = (Account,)
def run(self):
"""Main loop
Connect to Jabber server
@@ -242,68 +244,52 @@ class JCLComponent(Component, object):
self.__logger.debug("Signal %i received, shutting down..." % (signum,))
self.running = False
def apply_behavior(self, info_query, \
account_handler, \
account_type_handler, \
root_handler, \
send_result = False):
bare_from_jid = info_query.get_from().bare()
to_jid = info_query.get_to()
name = to_jid.node
account_type = to_jid.resource
if name is not None: # account
self.__logger.debug("Applying behavior on account " + name)
result = account_handler(name, bare_from_jid, account_type)
elif account_type is None: # root
self.__logger.debug("Applying behavior on root node")
result = root_handler(name, bare_from_jid, account_type)
else: # account type
self.__logger.debug("Applying behavior on account type " + account_type)
result = account_type_handler(name, bare_from_jid, account_type)
if send_result:
self._logger.debug("Sending responses")
for stanza in result:
self.stream.send(stanza)
return result
def disco_get_info(self, node, info_query):
"""Discovery get info handler
"""
self.__logger.debug("DISCO_GET_INFO")
disco_info = DiscoInfo()
if node is not None:
disco_info.add_feature("jabber:iq:register")
else:
disco_info.add_feature("jabber:iq:version")
if len(self.account_classes) == 1:
disco_info.add_feature("jabber:iq:register")
DiscoIdentity(disco_info, self.name,
self.disco_identity.category,
self.disco_identity.type)
return disco_info
return self.apply_behavior(info_query, \
lambda name, bare_from_jid, account_type: \
self.account_manager.account_disco_get_info(), \
lambda name, bare_from_jid, account_type: \
self.account_manager.account_type_disco_get_info, \
lambda name, bare_from_jid, account_type: \
self.account_manager.root_disco_get_info(self.name, \
self.disco_identity.category, \
self.disco_identity.type))
def disco_get_items(self, node, info_query):
"""Discovery get nested nodes handler
"""
self.__logger.debug("DISCO_GET_ITEMS")
base_from_jid = unicode(info_query.get_from().bare())
disco_items = DiscoItems()
if not node: # first level
if len(self.account_classes) == 1: # list accounts with only one type declared
regexp_type = re.compile("(.*)Account$")
match = regexp_type.search(self.account_classes[0].__name__)
if match is not None:
account_type = match.group(1)
self._list_accounts(disco_items, self.account_classes[0], \
base_from_jid, account_type)
else:
self.__logger.error(self.account_classes[0].__name__ + \
" name not well formed")
else: # list account types (when multiples accounts types)
for account_class in self.account_classes:
regexp_type = re.compile("(.*)Account$")
match = regexp_type.search(account_class.__name__)
if match is not None:
account_type = match.group(1)
item_jid = JID(unicode(self.jid) + "/" + account_type)
DiscoItem(disco_items, \
item_jid, \
account_type, \
account_type)
else:
self.__logger.error(account_class.__name__ + \
" name not well formed")
else: # second level
nodes = node.split("/");
if len(nodes) == 1 \
and len(self.account_classes) > 1: # second level only with multiples account types
self.__logger.debug("Listing account for " + nodes[0])
account_class = self._get_account_class(nodes[0] + "Account")
if account_class is not None:
self._list_accounts(disco_items, \
account_class, \
base_from_jid,
account_type = nodes[0])
else:
self.__logger.error("Error: " + account_class.__name__ \
+ " class not in account_classes")
return disco_items
return self.apply_behavior(info_query, \
lambda name, bare_from_jid, account_type: DiscoItems(), \
lambda name, bare_from_jid, account_type: \
self.account_manager.account_type_disco_get_items(bare_from_jid, account_type), \
lambda name, bare_from_jid, account_type:
self.account_manager.root_disco_get_items(bare_from_jid))
def handle_get_version(self, info_query):
"""Get Version handler
@@ -661,6 +647,7 @@ class JCLComponent(Component, object):
###########################################################################
# Utils
###########################################################################
# TODO : delete
def _get_account_class(self, account_class_name):
"""Return account class definition from declared classes in
account_classes from its class name"""
@@ -672,23 +659,6 @@ class JCLComponent(Component, object):
self.__logger.debug(account_class_name + " not found")
return None
def _list_accounts(self, disco_items, _account_class, base_from_jid, account_type = ""):
"""List accounts in disco_items for given _account_class and user jid"""
if account_type is not None and account_type != "":
resource = "/" + account_type
account_type = account_type + "/"
else:
resource = ""
self.db_connect()
for _account in _account_class.select(_account_class.q.user_jid == \
base_from_jid):
self.__logger.debug(str(_account))
DiscoItem(disco_items, \
JID(unicode(_account.jid) + resource), \
account_type + _account.name, \
_account.long_name)
self.db_disconnect()
def _send_presence_available(self, _account, show, lang_class):
"""Send available presence to account's user and ask for password
if necessary"""
@@ -816,3 +786,129 @@ class JCLComponent(Component, object):
Called regularly
"""
raise NotImplementedError
class AccountManager(object):
"""Implement component account behavior"""
def __init__(self, component):
"""AccountManager constructor"""
self.__logger = logging.getLogger("jcl.jabber.JCLComponent")
self.account_classes = (Account,)
self.component = component
def _get_account_classes(self):
"""account_classes getter"""
return self._account_classes
def _set_account_classes(self, account_classes):
"""account_classes setter"""
self._account_classes = account_classes
self.has_multiple_account_type = (len(self._account_classes) > 1)
account_classes = property(_get_account_classes, _set_account_classes)
###### disco_get_info handlers ######
def account_disco_get_info(self):
"""Implement discovery get_info on an account node"""
self.__logger.debug("account_disco_get_info")
disco_info = DiscoInfo()
disco_info.add_feature("jabber:iq:register")
return disco_info
def account_type_disco_get_info(self):
"""Implement discovery get_info on an account type node"""
self.__logger.debug("account_type_disco_get_info")
return self.account_disco_get_info()
def root_disco_get_info(self, name, category, type):
"""Implement discovery get_info on main component JID"""
self.__logger.debug("root_disco_get_info")
disco_info = DiscoInfo()
disco_info.add_feature("jabber:iq:version")
if not self.has_multiple_account_type:
disco_info.add_feature("jabber:iq:register")
DiscoIdentity(disco_info, name, \
category, \
type)
return disco_info
###### disco_get_items handlers ######
def account_type_disco_get_items(self, bare_from_jid, account_type):
"""Discovery get_items on an account type node"""
self.__logger.debug("Listing account for " + account_type)
disco_items = DiscoItems()
account_class = self._get_account_class(account_type + "Account")
if account_class is not None:
self._list_accounts(disco_items, \
account_class, \
bare_from_jid, \
account_type = account_type)
else:
self.__logger.error("Error: " + account_class.__name__ \
+ " class not in account_classes")
return disco_items
def root_disco_get_items(self, bare_from_jid):
"""Discovery get_items on root node"""
disco_items = DiscoItems()
regexp_type = re.compile("(.*)Account$")
if self.has_multiple_account_type: # list accounts with only one type declared
list_func = lambda disco_items, account_class, bare_from_jid, account_type: \
DiscoItem(disco_items, \
JID(unicode(self.component.jid) + "/" + account_type), \
account_type, \
account_type)
else:
list_func = self._list_accounts
for account_class in self.account_classes:
match = regexp_type.search(account_class.__name__)
if match is not None:
account_type = match.group(1)
list_func(disco_items, account_class, \
bare_from_jid, account_type)
else:
self.__logger.error(account_class.__name__ + \
" name not well formed")
return disco_items
###### Utils methods ######
def _list_accounts(self, disco_items, _account_class, bare_from_jid, account_type = ""):
"""List accounts in disco_items for given _account_class and user jid"""
if account_type is not None and account_type != "":
resource = "/" + account_type
account_type = account_type + "/"
else:
resource = ""
self.db_connect()
for _account in _account_class.select(_account_class.q.user_jid == \
unicode(bare_from_jid)):
self.__logger.debug(str(_account))
DiscoItem(disco_items, \
JID(unicode(_account.jid) + resource), \
account_type + _account.name, \
_account.long_name)
self.db_disconnect()
def _get_account_class(self, account_class_name):
"""Return account class definition from declared classes in
account_classes from its class name"""
self.__logger.debug("Looking for " + account_class_name)
for _account_class in self.account_classes:
if _account_class.__name__.lower() == account_class_name.lower():
self.__logger.debug(account_class_name + " found")
return _account_class
self.__logger.debug(account_class_name + " not found")
return None
def db_connect(self):
"""Create a new connection to the DataBase (SQLObject use connection
pool) associated to the current thread"""
account.hub.threadConnection = \
connectionForURI(self.component.db_connection_str) # TODO : move db_connection_str to AccountManager
# account.hub.threadConnection.debug = True
def db_disconnect(self):
"""Delete connection associated to the current thread"""
del account.hub.threadConnection