Basic Ad-Hoc commands support

Support commands listing (disco get items) and command info (disco get info)

darcs-hash:20070620164706-86b55-11fb94510d349169ab0450717a680db4440cd9b3.gz
This commit is contained in:
David Rousselie
2007-06-20 18:47:06 +02:00
parent f79c1ee4bf
commit c4daddd569
9 changed files with 177 additions and 76 deletions

View File

@@ -1,2 +1,29 @@
"""Jabber related classes""" """Jabber related classes"""
__revision__ = "" __revision__ = ""
from jcl.model.account import Account
class Handler(object):
"""handling class"""
def filter(self, stanza, lang_class):
"""Filter account to be processed by the handler
return all accounts. DB connection might already be opened."""
accounts = Account.select()
return accounts
def handle(self, stanza, lang_class, accounts):
"""Apply actions to do on given accounts
Do nothing by default"""
return []
class DiscoHandler(object):
"""Handle disco get items requests"""
def filter(self, node, info_query):
"""Filter requests to be handled"""
return False
def handle(self, disco_items, node, info_query, data, lang_class):
"""Handle disco get items request"""
return None

View File

@@ -50,7 +50,11 @@ from pyxmpp.presence import Presence
from pyxmpp.jabber.dataforms import Form, Field, Option from pyxmpp.jabber.dataforms import Form, Field, Option
import jcl import jcl
from jcl.jabber.error import FieldError from jcl.jabber import Handler
from jcl.error import FieldError
import jcl.jabber.command as command
from jcl.jabber.command import CommandDiscoGetItemsHandler, \
CommandDiscoGetInfoHandler, CommandManager, JCLCommandManager
from jcl.model import account from jcl.model import account
from jcl.model.account import Account, LegacyJID from jcl.model.account import Account, LegacyJID
from jcl.lang import Lang from jcl.lang import Lang
@@ -98,6 +102,11 @@ class JCLComponent(Component, object):
self.unsubscribe_handlers = [] self.unsubscribe_handlers = []
self.available_handlers = [] self.available_handlers = []
self.unavailable_handlers = [] self.unavailable_handlers = []
command.command_manager = JCLCommandManager()
command.command_manager.component = self
command.command_manager.account_manager = self.account_manager
self.disco_get_items_handlers = [CommandDiscoGetItemsHandler()]
self.disco_get_info_handlers = [CommandDiscoGetInfoHandler()]
self.__logger = logging.getLogger("jcl.jabber.JCLComponent") self.__logger = logging.getLogger("jcl.jabber.JCLComponent")
self.lang = lang self.lang = lang
@@ -322,10 +331,14 @@ class JCLComponent(Component, object):
self.stream.send(info_query) self.stream.send(info_query)
return 1 return 1
def disco_get_commands_list():
"""Return Ad-Hoc commands list"""
return None
def disco_get_info(self, node, info_query): def disco_get_info(self, node, info_query):
"""Discovery get info handler """Discovery get info handler
""" """
return self.apply_behavior(\ result = self.apply_behavior(\
info_query, info_query,
lambda name, from_jid, account_type, lang_class: \ lambda name, from_jid, account_type, lang_class: \
self.account_manager.account_disco_get_info(), self.account_manager.account_disco_get_info(),
@@ -333,23 +346,40 @@ class JCLComponent(Component, object):
self.account_manager.account_type_disco_get_info(), self.account_manager.account_type_disco_get_info(),
lambda name, from_jid, account_type, lang_class: \ lambda name, from_jid, account_type, lang_class: \
self.account_manager.root_disco_get_info(\ self.account_manager.root_disco_get_info(\
node,
self.name, self.name,
self.disco_identity.category, self.disco_identity.category,
self.disco_identity.type)) self.disco_identity.type))
if result is None:
lang_class = self.lang.get_lang_class_from_node(info_query.get_node())
for disco_filter in self.disco_get_info_handlers:
data = disco_filter.filter(node, info_query)
if data:
result = disco_filter.handle(result, node, info_query,
data, lang_class)
return result
def disco_get_items(self, node, info_query): def disco_get_items(self, node, info_query):
"""Discovery get nested nodes handler """Discovery get nested nodes handler
""" """
return self.apply_behavior(\ result = self.apply_behavior(\
info_query, info_query,
lambda name, from_jid, account_type, lang_class: \ lambda name, from_jid, account_type, lang_class: \
DiscoItems(), None,
lambda name, from_jid, account_type, lang_class: \ lambda name, from_jid, account_type, lang_class: \
self.account_manager.account_type_disco_get_items(from_jid, self.account_manager.account_type_disco_get_items(from_jid,
account_type), account_type),
lambda name, from_jid, account_type, lang_class: \ lambda name, from_jid, account_type, lang_class: \
self.account_manager.root_disco_get_items(from_jid, self.account_manager.root_disco_get_items(from_jid,
lang_class)) lang_class))
if result is None:
lang_class = self.lang.get_lang_class_from_node(info_query.get_node())
for disco_filter in self.disco_get_items_handlers:
data = disco_filter.filter(node, info_query)
if data:
result = disco_filter.handle(result, node, info_query,
data, lang_class)
return result
def handle_get_version(self, info_query): def handle_get_version(self, info_query):
"""Get Version handler """Get Version handler
@@ -569,8 +599,10 @@ class AccountManager(object):
def __init__(self, component): def __init__(self, component):
"""AccountManager constructor""" """AccountManager constructor"""
self.__logger = logging.getLogger("jcl.jabber.JCLComponent") self.__logger = logging.getLogger("jcl.jabber.JCLComponent")
self.regexp_type = re.compile("(.*)Account$")
self.account_classes = (Account,) self.account_classes = (Account,)
self.component = component self.component = component
self.account_types = []
def _get_account_classes(self): def _get_account_classes(self):
"""account_classes getter""" """account_classes getter"""
@@ -580,6 +612,16 @@ class AccountManager(object):
"""account_classes setter""" """account_classes setter"""
self._account_classes = account_classes self._account_classes = account_classes
self.has_multiple_account_type = (len(self._account_classes) > 1) self.has_multiple_account_type = (len(self._account_classes) > 1)
self.account_types = []
for account_class in account_classes:
match = self.regexp_type.search(account_class.__name__)
if match is not None:
account_type = match.group(1)
self.account_types.append(account_type)
else:
self.__logger.error(account_class.__name__ +
" name not well formed")
self.account_types.append("")
account_classes = property(_get_account_classes, _set_account_classes) account_classes = property(_get_account_classes, _set_account_classes)
@@ -596,9 +638,10 @@ class AccountManager(object):
self.__logger.debug("account_type_disco_get_info") self.__logger.debug("account_type_disco_get_info")
return self.account_disco_get_info() return self.account_disco_get_info()
def root_disco_get_info(self, name, category, type): def root_disco_get_info(self, node, name, category, type):
"""Implement discovery get_info on main component JID""" """Implement discovery get_info on main component JID"""
self.__logger.debug("root_disco_get_info") self.__logger.debug("root_disco_get_info")
if not node:
disco_info = DiscoInfo() disco_info = DiscoInfo()
disco_info.add_feature("jabber:iq:version") disco_info.add_feature("jabber:iq:version")
if not self.has_multiple_account_type: if not self.has_multiple_account_type:
@@ -607,27 +650,26 @@ class AccountManager(object):
category, category,
type) type)
return disco_info return disco_info
else:
return None
###### disco_get_items handlers ###### ###### disco_get_items handlers ######
def account_type_disco_get_items(self, from_jid, account_type): def account_type_disco_get_items(self, from_jid, account_type):
"""Discovery get_items on an account type node""" """Discovery get_items on an account type node"""
self.__logger.debug("Listing account for " + account_type) self.__logger.debug("Listing account for " + account_type)
disco_items = DiscoItems()
account_class = self._get_account_class(account_type + "Account") account_class = self._get_account_class(account_type + "Account")
if account_class is not None: if account_class is not None:
self._list_accounts(disco_items, return self._list_accounts(account_class,
account_class,
from_jid.bare(), from_jid.bare(),
account_type=account_type) account_type=account_type)
else: else:
self.__logger.error("Error: " + account_class.__name__ self.__logger.error("Error: " + account_class.__name__
+ " class not in account_classes") + " class not in account_classes")
return disco_items return None
def root_disco_get_items(self, from_jid, lang_class): def root_disco_get_items(self, from_jid, lang_class):
"""Discovery get_items on root node""" """Discovery get_items on root node"""
disco_items = DiscoItems() disco_items = None
regexp_type = re.compile("(.*)Account$")
if self.has_multiple_account_type: # list accounts with only one type declared if self.has_multiple_account_type: # list accounts with only one type declared
def _list_account_types(disco_items, account_class, bare_from_jid, account_type): def _list_account_types(disco_items, account_class, bare_from_jid, account_type):
type_label_attr = "type_" + account_type.lower() + "_name" type_label_attr = "type_" + account_type.lower() + "_name"
@@ -640,20 +682,15 @@ class AccountManager(object):
account_type), account_type),
account_type, account_type,
type_label) type_label)
list_func = _list_account_types disco_items = DiscoItems()
for account_type in self.account_types:
else: _list_account_types(disco_items,
list_func = self._list_accounts self._get_account_class(account_type
+ "Account"),
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,
from_jid.bare(), account_type) from_jid.bare(), account_type)
else: else:
self.__logger.error(account_class.__name__ + disco_items = self._list_accounts(self.account_classes[0],
" name not well formed") from_jid.bare())
return disco_items return disco_items
###### get_register handlers ###### ###### get_register handlers ######
@@ -1014,7 +1051,7 @@ class AccountManager(object):
return result return result
def _list_accounts(self, disco_items, _account_class, bare_from_jid, def _list_accounts(self, _account_class, bare_from_jid,
account_type=""): account_type=""):
"""List accounts in disco_items for given _account_class and user jid""" """List accounts in disco_items for given _account_class and user jid"""
if account_type is not None and account_type != "": if account_type is not None and account_type != "":
@@ -1023,14 +1060,19 @@ class AccountManager(object):
else: else:
resource = "" resource = ""
self.db_connect() self.db_connect()
for _account in _account_class.select(_account_class.q.user_jid == \ accounts = _account_class.select(_account_class.q.user_jid == \
unicode(bare_from_jid)): unicode(bare_from_jid))
if accounts.count() == 0:
return None
disco_items = DiscoItems()
for _account in accounts:
self.__logger.debug(str(_account)) self.__logger.debug(str(_account))
DiscoItem(disco_items, DiscoItem(disco_items,
JID(unicode(_account.jid) + resource), JID(unicode(_account.jid) + resource),
account_type + _account.name, account_type + _account.name,
_account.long_name) _account.long_name)
self.db_disconnect() self.db_disconnect()
return disco_items
def _get_account_class(self, account_class_name): def _get_account_class(self, account_class_name):
"""Return account class definition from declared classes in """Return account class definition from declared classes in
@@ -1221,20 +1263,6 @@ class AccountManager(object):
% (exception))) % (exception)))
return result return result
class Handler(object):
"""handling class"""
def filter(self, stanza, lang_class):
"""Filter account to be processed by the handler
return all accounts. DB connection might already be opened."""
accounts = Account.select()
return accounts
def handle(self, stanza, lang_class, accounts):
"""Apply actions to do on given accounts
Do nothing by default"""
return []
class DefaultPresenceHandler(Handler): class DefaultPresenceHandler(Handler):
"""Handle presence""" """Handle presence"""
@@ -1313,4 +1341,3 @@ class PasswordMessageHandler(Handler):
to_jid=stanza.get_from(), to_jid=stanza.get_from(),
subject=lang_class.password_saved_for_session, subject=lang_class.password_saved_for_session,
body=lang_class.password_saved_for_session)] body=lang_class.password_saved_for_session)]

View File

@@ -28,7 +28,8 @@ __revision__ = "$Id: feeder.py,v 1.3 2005/09/18 20:24:07 dax Exp $"
import logging import logging
from jcl.jabber.component import JCLComponent, Handler from jcl.jabber import Handler
from jcl.jabber.component import JCLComponent
from jcl.lang import Lang from jcl.lang import Lang
from jcl.model.account import Account from jcl.model.account import Account

View File

@@ -533,6 +533,28 @@ class JCLComponent_TestCase(unittest.TestCase):
info_query) info_query)
self.assertTrue(disco_info.has_feature("jabber:iq:register")) self.assertTrue(disco_info.has_feature("jabber:iq:register"))
def test_disco_get_info_root_unknown_node(self):
info_query = Iq(stanza_type="get",
from_jid="user1@test.com",
to_jid="jcl.test.com")
disco_info = self.comp.disco_get_info("unknown", info_query)
self.assertEquals(disco_info, None)
def test_disco_get_info_command_list(self):
info_query = Iq(stanza_type="get",
from_jid="user1@test.com",
to_jid="jcl.test.com")
disco_info = self.comp.disco_get_info("list", info_query)
self.assertNotEquals(disco_info, None)
self.assertTrue(disco_info.has_feature("http://jabber.org/protocol/commands"))
self.assertEquals(len(disco_info.get_identities()), 1)
self.assertEquals(disco_info.get_identities()[0].get_category(),
"automation")
self.assertEquals(disco_info.get_identities()[0].get_type(),
"command-node")
self.assertEquals(disco_info.get_identities()[0].get_name(),
Lang.en.command_list)
########################################################################### ###########################################################################
# 'disco_get_items' tests # 'disco_get_items' tests
########################################################################### ###########################################################################
@@ -546,25 +568,32 @@ class JCLComponent_TestCase(unittest.TestCase):
info_query = Iq(stanza_type = "get", \ info_query = Iq(stanza_type = "get", \
from_jid = "user1@test.com", \ from_jid = "user1@test.com", \
to_jid = "jcl.test.com") to_jid = "jcl.test.com")
disco_items = self.comp.disco_get_items(None, info_query) disco_items = self.comp.disco_get_items("unknown", info_query)
self.assertEquals(len(disco_items.get_items()), 1) self.assertEquals(len(disco_items.get_items()), 1)
disco_item = disco_items.get_items()[0] disco_item = disco_items.get_items()[0]
self.assertEquals(disco_item.get_jid(), account1.jid) self.assertEquals(disco_item.get_jid(), account1.jid)
self.assertEquals(disco_item.get_node(), account1.name) self.assertEquals(disco_item.get_node(), account1.name)
self.assertEquals(disco_item.get_name(), account1.long_name) self.assertEquals(disco_item.get_name(), account1.long_name)
def test_disco_get_items_unknown_node(self):
info_query = Iq(stanza_type="get",
from_jid="user1@test.com",
to_jid="jcl.test.com")
disco_items = self.comp.disco_get_items(None, info_query)
self.assertEquals(disco_items, None)
def test_disco_get_items_1type_with_node(self): def test_disco_get_items_1type_with_node(self):
"""get_items on an account. Must return nothing""" """get_items on an account. Must return nothing"""
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account1 = Account(user_jid = "user1@test.com", \ account1 = Account(user_jid="user1@test.com",
name = "account1", \ name="account1",
jid = "account1@jcl.test.com") jid="account1@jcl.test.com")
del account.hub.threadConnection del account.hub.threadConnection
info_query = Iq(stanza_type = "get", \ info_query = Iq(stanza_type="get",
from_jid = "user1@test.com", \ from_jid="user1@test.com",
to_jid = "account1@jcl.test.com") to_jid="account1@jcl.test.com")
disco_items = self.comp.disco_get_items("account1", info_query) disco_items = self.comp.disco_get_items("account1", info_query)
self.assertEquals(disco_items.get_items(), []) self.assertEquals(disco_items, None)
def test_disco_get_items_2types_no_node(self): def test_disco_get_items_2types_no_node(self):
"""get_items on main entity. Must account types""" """get_items on main entity. Must account types"""
@@ -663,7 +692,7 @@ class JCLComponent_TestCase(unittest.TestCase):
from_jid = "user1@test.com", \ from_jid = "user1@test.com", \
to_jid = "account1@jcl.test.com/Example") to_jid = "account1@jcl.test.com/Example")
disco_items = self.comp.disco_get_items("Example/account1", info_query) disco_items = self.comp.disco_get_items("Example/account1", info_query)
self.assertEquals(disco_items.get_items(), []) self.assertEquals(disco_items, None)
def test_disco_get_items_2types_with_long_node2(self): def test_disco_get_items_2types_with_long_node2(self):
"""get_items on a second type account. Must return nothing""" """get_items on a second type account. Must return nothing"""
@@ -677,7 +706,18 @@ class JCLComponent_TestCase(unittest.TestCase):
from_jid = "user1@test.com", \ from_jid = "user1@test.com", \
to_jid = "account1@jcl.test.com/Example2") to_jid = "account1@jcl.test.com/Example2")
disco_items = self.comp.disco_get_items("Example2/account1", info_query) disco_items = self.comp.disco_get_items("Example2/account1", info_query)
self.assertEquals(disco_items.get_items(), []) self.assertEquals(disco_items, None)
def test_disco_get_items_list_commands(self):
info_query = Iq(stanza_type="get",
from_jid="user1@test.com",
to_jid="jcl.test.com")
disco_items = self.comp.disco_get_items("http://jabber.org/protocol/commands",
info_query)
self.assertEquals(len(disco_items.get_items()), 1)
item = disco_items.get_items()[0]
self.assertEquals(item.get_node(), "list")
self.assertEquals(item.get_name(), Lang.en.command_list)
########################################################################### ###########################################################################
# 'handle_get_version' tests # 'handle_get_version' tests

View File

@@ -110,6 +110,8 @@ class Lang:
get_gateway_desc = u"Please enter the email address of your contact" get_gateway_desc = u"Please enter the email address of your contact"
get_gateway_prompt = u"Email address" get_gateway_prompt = u"Email address"
command_list = u"List accounts"
class fr: class fr:
component_name = u"composant générique Jabber Component Library" component_name = u"composant générique Jabber Component Library"
register_title = u"Enregistrement d'une nouvelle connexion" register_title = u"Enregistrement d'une nouvelle connexion"
@@ -151,6 +153,8 @@ class Lang:
get_gateway_desc = u"Entrer l'adresse email de votre contact" get_gateway_desc = u"Entrer l'adresse email de votre contact"
get_gateway_prompt = u"Adresse email" get_gateway_prompt = u"Adresse email"
command_list = u"List les comptes"
class nl: class nl:
# TODO: when finish, delete this line and uncomment in tests/lang.py the makeSuite(Language_nl_TestCase, 'test') line # TODO: when finish, delete this line and uncomment in tests/lang.py the makeSuite(Language_nl_TestCase, 'test') line
register_title = u"Registratie van verbindingen voor Jabber Mail" register_title = u"Registratie van verbindingen voor Jabber Mail"

View File

@@ -32,7 +32,7 @@ from sqlobject.dbconnection import ConnectionHub
from sqlobject.joins import MultipleJoin from sqlobject.joins import MultipleJoin
from jcl.lang import Lang from jcl.lang import Lang
from jcl.jabber.error import FieldError from jcl.error import FieldError
OFFLINE = "offline" OFFLINE = "offline"
ONLINE = "online" ONLINE = "online"

View File

@@ -27,7 +27,7 @@ import os
from sqlobject import * from sqlobject import *
from sqlobject.dbconnection import TheURIOpener from sqlobject.dbconnection import TheURIOpener
from jcl.jabber.error import FieldError from jcl.error import FieldError
from jcl.model import account from jcl.model import account
from jcl.model.account import Account, PresenceAccount from jcl.model.account import Account, PresenceAccount

View File

@@ -114,6 +114,8 @@ class Language_TestCase(unittest.TestCase):
self.assertNotEquals(self.lang_class.get_gateway_desc, None) self.assertNotEquals(self.lang_class.get_gateway_desc, None)
self.assertNotEquals(self.lang_class.get_gateway_prompt, None) self.assertNotEquals(self.lang_class.get_gateway_prompt, None)
self.assertNotEquals(self.lang_class.command_list, None)
class Language_fr_TestCase(Language_TestCase): class Language_fr_TestCase(Language_TestCase):
def setUp(self): def setUp(self):
self.lang_class = Lang.fr self.lang_class = Lang.fr