Some refactoring

Put disco and presence handlers in configurable Handler classes

darcs-hash:20070708140132-86b55-48a41b751c68fb60620fe883f38b7c3104fdf796.gz
This commit is contained in:
David Rousselie
2007-07-08 16:01:32 +02:00
parent a9534ee40f
commit 7507f3b6ab
17 changed files with 1233 additions and 999 deletions

View File

@@ -1,11 +1,16 @@
"""Jabber related classes""" """Jabber related classes"""
__revision__ = "" __revision__ = ""
import jcl.model.account as account
from jcl.model.account import Account from jcl.model.account import Account
class Handler(object): class Handler(object):
"""handling class""" """handling class"""
def __init__(self, component):
"""Default Handler constructor"""
self.component = component
def filter(self, stanza, lang_class): def filter(self, stanza, lang_class):
""" """
Filter account to be processed by the handler Filter account to be processed by the handler
@@ -21,13 +26,41 @@ class Handler(object):
""" """
return [] return []
class DiscoHandler(object): def root_filter(self, stanza, lang_class, node=None):
"""Handle disco get items requests""" """Filter stanza sent to root node"""
to_jid = stanza.get_to()
def filter(self, node, info_query): if to_jid.resource is None and to_jid.node is None and node is None:
"""Filter requests to be handled""" return True
return False else:
return None
def handle(self, node, info_query, lang_class, disco_items, data):
"""Handle disco get items request""" def account_type_filter(self, stanza, lang_class, node=None):
"""Filter stanzas sent to account type node"""
to_jid = stanza.get_to()
account_type = to_jid.resource
if account_type is not None and to_jid.node is None:
return account_type
else:
return None
def account_filter(self, stanza, lang_class, node=None):
"""Filter stanzas sent to account jid"""
name = stanza.get_to().node
return name
def get_account_filter(self, stanza, lang_class, node=None):
"""Filter stanzas sent to account jid, only if account exists"""
name = stanza.get_to().node
if name is not None:
return account.get_account(name,
stanza.get_from().bare())
else:
return None
def get_accounts_root_filter(self, stanza, lang_class, node=None):
"""Filter stanza sent to root node"""
to_jid = stanza.get_to()
if to_jid.resource is None and to_jid.node is None and node is None:
return account.get_accounts(stanza.get_from().bare())
else:
return None return None

View File

@@ -27,7 +27,8 @@ from pyxmpp.jabber.dataforms import Form, Field
import jcl import jcl
from jcl.lang import Lang from jcl.lang import Lang
from jcl.jabber import DiscoHandler from jcl.jabber.disco import DiscoHandler, RootDiscoGetInfoHandler
import jcl.model as model
from jcl.model.account import Account from jcl.model.account import Account
COMMAND_NS = "http://jabber.org/protocol/commands" COMMAND_NS = "http://jabber.org/protocol/commands"
@@ -146,47 +147,60 @@ class JCLCommandManager(CommandManager):
field_type="fixed", field_type="fixed",
label="Account name")) # TODO: add to Lang label="Account name")) # TODO: add to Lang
bare_from_jid = unicode(info_query.get_from().bare()) bare_from_jid = unicode(info_query.get_from().bare())
self.account_manager.db_connect() model.db_connect()
accounts = Account.select(Account.q.user_jid == bare_from_jid) accounts = Account.select(Account.q.user_jid == bare_from_jid)
for _account in accounts: for _account in accounts:
fields = [Field(name="name", fields = [Field(name="name",
field_type="fixed", field_type="fixed",
value=_account.name)] value=_account.name)]
result_form.add_item(fields) result_form.add_item(fields)
self.account_manager.db_disconnect() model.db_disconnect()
result_form.as_xml(command_node) result_form.as_xml(command_node)
return [response] return [response]
class CommandRootDiscoGetInfoHandler(RootDiscoGetInfoHandler):
def handle(self, info_query, lang_class, node, disco_obj, data):
"""Add command feature to DiscoInfo"""
disco_infos = RootDiscoGetInfoHandler.handle(self, info_query,
lang_class, node,
disco_obj, data)
disco_infos[0].add_feature(COMMAND_NS)
return disco_infos
class CommandDiscoGetInfoHandler(DiscoHandler): class CommandDiscoGetInfoHandler(DiscoHandler):
"""Handle Ad-Hoc command disco get info requests""" """Handle Ad-Hoc command disco get info requests"""
def filter(self, node, info_query): def filter(self, info_query, lang_class, node):
""" """
Filter requests to be handled. Only known commands Filter requests to be handled. Only known commands
""" """
return (node in command_manager.commands) return (node in command_manager.commands)
def handle(self, disco_info, node, info_query, data, lang_class): def handle(self, info_query, lang_class, node, disco_obj, data):
""" """
Return infos for given command Return infos for given command
""" """
if not disco_info: if not disco_obj:
disco_info = DiscoInfo() disco_obj = DiscoInfo()
return command_manager.get_command_info(disco_info, node, lang_class) return [command_manager.get_command_info(disco_info=disco_obj,
command_name=node,
lang_class=lang_class)]
class CommandDiscoGetItemsHandler(DiscoHandler): class CommandDiscoGetItemsHandler(DiscoHandler):
"""Handle Ad-Hoc command disco get items requests""" """Handle Ad-Hoc command disco get items requests"""
def filter(self, node, info_query): def filter(self, info_query, lang_class, node):
""" """
Filter requests to be handled. Filter requests to be handled.
""" """
return (node == 'http://jabber.org/protocol/commands') return (node == 'http://jabber.org/protocol/commands')
def handle(self, disco_items, node, info_query, data, lang_class): def handle(self, info_query, lang_class, node, disco_obj, data):
""" """
""" """
if not disco_items: if not disco_obj:
disco_items = DiscoItems() disco_obj = DiscoItems()
return command_manager.list_commands(disco_items, lang_class) return [command_manager.list_commands(disco_items=disco_obj,
lang_class=lang_class)]

View File

@@ -40,7 +40,6 @@ from Queue import Queue
from sqlobject.inheritance import InheritableSQLObject from sqlobject.inheritance import InheritableSQLObject
from sqlobject.sqlbuilder import AND from sqlobject.sqlbuilder import AND
from sqlobject.dbconnection import connectionForURI
import pyxmpp.error as error import pyxmpp.error as error
from pyxmpp.jid import JID from pyxmpp.jid import JID
@@ -53,10 +52,19 @@ from pyxmpp.jabber.dataforms import Form, Field, Option
import jcl import jcl
from jcl.error import FieldError from jcl.error import FieldError
from jcl.jabber import Handler from jcl.jabber import Handler
from jcl.jabber.disco import AccountDiscoGetInfoHandler, \
AccountTypeDiscoGetInfoHandler, RootDiscoGetItemsHandler, \
AccountTypeDiscoGetItemsHandler
from jcl.jabber.message import PasswordMessageHandler from jcl.jabber.message import PasswordMessageHandler
import jcl.jabber.command as command import jcl.jabber.command as command
from jcl.jabber.command import CommandDiscoGetItemsHandler, \ from jcl.jabber.command import CommandDiscoGetItemsHandler, \
CommandDiscoGetInfoHandler, CommandManager, JCLCommandManager CommandDiscoGetInfoHandler, CommandManager, JCLCommandManager, \
CommandRootDiscoGetInfoHandler
from jcl.jabber.presence import AccountPresenceAvailableHandler, \
RootPresenceAvailableHandler, AccountPresenceUnavailableHandler, \
RootPresenceUnavailableHandler, AccountPresenceSubscribeHandler, \
RootPresenceSubscribeHandler, AccountPresenceUnsubscribeHandler
import jcl.model as model
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
@@ -67,10 +75,12 @@ VERSION = "0.1"
# JCL implementation # JCL implementation
############################################################################### ###############################################################################
class JCLComponent(Component, object): class JCLComponent(Component, object):
"""Implement default JCL component behavior: """
Implement default JCL component behavior:
- Jabber register process (add, delete, update accounts) - Jabber register process (add, delete, update accounts)
- Jabber presence handling - Jabber presence handling
- passwork request at login - passwork request at login
- Service Administration (XEP-0133)
""" """
timeout = 1 timeout = 1
@@ -80,7 +90,6 @@ class JCLComponent(Component, object):
secret, secret,
server, server,
port, port,
db_connection_str,
disco_category="headline", disco_category="headline",
disco_type="x-unknown", disco_type="x-unknown",
lang=Lang()): lang=Lang()):
@@ -94,21 +103,28 @@ class JCLComponent(Component, object):
# default values # default values
self.name = lang.get_default_lang_class().component_name self.name = lang.get_default_lang_class().component_name
self.spool_dir = "." self.spool_dir = "."
self.db_connection_str = db_connection_str
self.version = VERSION self.version = VERSION
self.time_unit = 60 self.time_unit = 60
self.queue = Queue(100) self.queue = Queue(100)
self.account_manager = AccountManager(self) self.account_manager = AccountManager(self)
self.msg_handlers = [] self.msg_handlers = []
self.subscribe_handlers = [] self.presence_subscribe_handlers = [[AccountPresenceSubscribeHandler(self),
self.unsubscribe_handlers = [] RootPresenceSubscribeHandler(self)]]
self.available_handlers = [] self.presence_unsubscribe_handlers = [[AccountPresenceUnsubscribeHandler(self)]]
self.unavailable_handlers = [] self.presence_available_handlers = [[AccountPresenceAvailableHandler(self),
RootPresenceAvailableHandler(self)]]
self.presence_unavailable_handlers = [[AccountPresenceUnavailableHandler(self),
RootPresenceUnavailableHandler(self)]]
command.command_manager = JCLCommandManager() command.command_manager = JCLCommandManager()
command.command_manager.component = self command.command_manager.component = self
command.command_manager.account_manager = self.account_manager command.command_manager.account_manager = self.account_manager
self.disco_get_items_handlers = [CommandDiscoGetItemsHandler()] self.disco_get_items_handlers = [[RootDiscoGetItemsHandler(self),
self.disco_get_info_handlers = [CommandDiscoGetInfoHandler()] AccountTypeDiscoGetItemsHandler(self)],
[CommandDiscoGetItemsHandler(self)]]
self.disco_get_info_handlers = [[CommandRootDiscoGetInfoHandler(self),
AccountDiscoGetInfoHandler(self),
AccountTypeDiscoGetInfoHandler(self)],
[CommandDiscoGetInfoHandler(self)]]
self.__logger = logging.getLogger("jcl.jabber.JCLComponent") self.__logger = logging.getLogger("jcl.jabber.JCLComponent")
self.lang = lang self.lang = lang
@@ -119,7 +135,8 @@ class JCLComponent(Component, object):
signal.signal(signal.SIGTERM, self.signal_handler) signal.signal(signal.SIGTERM, self.signal_handler)
def run(self): def run(self):
"""Main loop """
Main loop
Connect to Jabber server Connect to Jabber server
Start timer thread Start timer thread
Call Component main loop Call Component main loop
@@ -149,27 +166,12 @@ class JCLComponent(Component, object):
self.disconnect() self.disconnect()
self.__logger.debug("Exitting normally") self.__logger.debug("Exitting normally")
#################
# SQlite connections are not multi-threaded
# Utils workaround methods
#################
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.db_connection_str)
# account.hub.threadConnection.debug = True
def db_disconnect(self):
"""Delete connection associated to the current thread"""
del account.hub.threadConnection
########################################################################### ###########################################################################
# Handlers # Handlers
########################################################################### ###########################################################################
def time_handler(self): def time_handler(self):
"""Timer thread handler """
Timer thread handler
""" """
self.__logger.info("Timer thread started...") self.__logger.info("Timer thread started...")
try: try:
@@ -179,13 +181,17 @@ class JCLComponent(Component, object):
self.wait_event.wait(self.time_unit) self.wait_event.wait(self.time_unit)
self.handle_tick() self.handle_tick()
self.__logger.debug("Resetting alarm signal") self.__logger.debug("Resetting alarm signal")
##time.sleep(self.time_unit)
except Exception, exception: except Exception, exception:
type, value, stack = sys.exc_info()
self.__logger.error("Error in timer thread\n%s\n%s"
% (exception, "".join(traceback.format_exception
(type, value, stack, 5))))
self.queue.put(exception) self.queue.put(exception)
self.__logger.info("Timer thread terminated...") self.__logger.info("Timer thread terminated...")
def authenticated(self): def authenticated(self):
"""Override authenticated Component event handler """
Override authenticated Component event handler
Register event handlers Register event handlers
Probe for every accounts registered Probe for every accounts registered
""" """
@@ -227,7 +233,7 @@ class JCLComponent(Component, object):
self.handle_message) self.handle_message)
self.send_stanzas(self.account_manager.probe_all_accounts_presence()) self.send_stanzas(self.account_manager.probe_all_accounts_presence())
self.msg_handlers += [PasswordMessageHandler()] self.msg_handlers += [[PasswordMessageHandler(self)]]
def signal_handler(self, signum, frame): def signal_handler(self, signum, frame):
"""Stop method handler """Stop method handler
@@ -277,32 +283,45 @@ class JCLComponent(Component, object):
self.send_stanzas(result) self.send_stanzas(result)
return result return result
def apply_registered_behavior(self, handlers, stanza, apply_all=True): def apply_registered_behavior(self, handlers, stanza,
apply_filter_func=None,
apply_handle_func=None):
"""Execute handler if their filter method does not return None""" """Execute handler if their filter method does not return None"""
result = [] result = []
self.db_connect() lang_class = self.lang.get_lang_class_from_node(stanza.get_node())
lang = self.lang.get_lang_class_from_node(stanza.get_node()) for handler_group in handlers:
for handler in handlers: for handler in handler_group:
try: try:
self.__logger.debug("Applying filter " + repr(handler)) self.__logger.debug("Applying filter " + repr(handler))
data = handler.filter(stanza, lang) if apply_filter_func is not None:
if data: data = apply_filter_func(handler.filter, stanza, lang_class)
self.__logger.debug("Applying handler " + repr(handler)) else:
result += handler.handle(stanza, lang, data) data = handler.filter(stanza, lang_class)
if not apply_all: if data is not None and data != False:
break self.__logger.debug("Applying handler " + repr(handler))
except Exception, e: if apply_handle_func is not None:
type, value, stack = sys.exc_info() handler_result = apply_handle_func(handler.handle,
self.__logger.error("Error with handler " + str(handler) + stanza,
" with " + str(stanza) + "\n%s\n%s" lang_class,
% (e, "".join(traceback.format_exception data,
(type, value, stack, 5)))) result)
result += [Message(from_jid=stanza.get_to(), else:
to_jid=stanza.get_from(), handler_result = handler.handle(stanza, lang_class,
stanza_type="error", data)
subject=lang.error_subject, if handler_result is not None:
body=lang.error_body % (e.message))] result += handler_result
self.db_disconnect() break
except Exception, e:
type, value, stack = sys.exc_info()
self.__logger.error("Error with handler " + str(handler) +
" with " + str(stanza) + "\n%s\n%s"
% (e, "".join(traceback.format_exception
(type, value, stack, 5))))
result += [Message(from_jid=stanza.get_to(),
to_jid=stanza.get_from(),
stanza_type="error",
subject=lang_class.error_subject,
body=lang_class.error_body % (e.message))]
self.send_stanzas(result) self.send_stanzas(result)
return result return result
@@ -337,50 +356,29 @@ class JCLComponent(Component, object):
return 1 return 1
def disco_get_info(self, node, info_query): def disco_get_info(self, node, info_query):
"""Discovery get info handler
""" """
result = self.apply_behavior(\ Discovery get info handler
info_query, """
lambda name, from_jid, account_type, lang_class: \ result = self.apply_registered_behavior(\
self.account_manager.account_disco_get_info(), self.disco_get_info_handlers,
lambda name, from_jid, account_type, lang_class: \ info_query,
self.account_manager.account_type_disco_get_info(), lambda filter_func, stanza, lang_class: \
lambda name, from_jid, account_type, lang_class: \ filter_func(stanza, lang_class, node),
self.account_manager.root_disco_get_info(\ lambda handle_func, stanza, lang_class, data, result: \
node, handle_func(stanza, lang_class, node, result, data))
self.name,
self.disco_identity.category,
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 return result
def disco_get_items(self, node, info_query): def disco_get_items(self, node, info_query):
"""Discovery get nested nodes handler
""" """
result = self.apply_behavior(\ Discovery get nested nodes handler
"""
result = self.apply_registered_behavior(\
self.disco_get_items_handlers,
info_query, info_query,
lambda name, from_jid, account_type, lang_class: \ lambda filter_func, stanza, lang_class: \
None, filter_func(stanza, lang_class, node),
lambda name, from_jid, account_type, lang_class: \ lambda handle_func, stanza, lang_class, data, result: \
self.account_manager.account_type_disco_get_items(from_jid, handle_func(stanza, lang_class, node, result, data))
account_type),
lambda name, from_jid, account_type, lang_class: \
self.account_manager.root_disco_get_items(node,
from_jid,
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 return result
def handle_get_version(self, info_query): def handle_get_version(self, info_query):
@@ -474,22 +472,7 @@ class JCLComponent(Component, object):
if presence sent to the component ('if not name'), presence is sent to if presence sent to the component ('if not name'), presence is sent to
all accounts for current user. Otherwise, send presence from current account. all accounts for current user. Otherwise, send presence from current account.
""" """
result = self.apply_registered_behavior(self.available_handlers, stanza) result = self.apply_registered_behavior(self.presence_available_handlers, stanza)
if result == []:
result = self.apply_behavior(\
stanza,
lambda name, from_jid, account_type, lang_class: \
self.account_manager.account_handle_presence_available(name,
from_jid,
lang_class,
stanza.get_show()),
lambda name, from_jid, account_type, lang_class: \
[],
lambda name, from_jid, account_type, lang_class: \
self.account_manager.root_handle_presence_available(from_jid,
lang_class,
stanza.get_show()),
send_result=True)
return result return result
@@ -497,38 +480,16 @@ class JCLComponent(Component, object):
"""Handle presence unavailability """Handle presence unavailability
""" """
self.__logger.debug("PRESENCE_UNAVAILABLE") self.__logger.debug("PRESENCE_UNAVAILABLE")
result = self.apply_registered_behavior(self.unavailable_handlers, stanza) result = self.apply_registered_behavior(self.presence_unavailable_handlers,
if result == []: stanza)
result = self.apply_behavior(\
stanza,
lambda name, from_jid, account_type, lang_class: \
self.account_manager.account_handle_presence_unavailable(name,
from_jid),
lambda name, from_jid, account_type, lang_class: \
[],
lambda name, from_jid, account_type, lang_class: \
self.account_manager.root_handle_presence_unavailable(from_jid),
send_result=True)
return result return result
def handle_presence_subscribe(self, stanza): def handle_presence_subscribe(self, stanza):
"""Handle subscribe presence from user """Handle subscribe presence from user
""" """
self.__logger.debug("PRESENCE_SUBSCRIBE") self.__logger.debug("PRESENCE_SUBSCRIBE")
result = self.apply_registered_behavior(self.subscribe_handlers, stanza) result = self.apply_registered_behavior(self.presence_subscribe_handlers,
if result == []: stanza)
result = self.apply_behavior(\
stanza,
lambda name, from_jid, account_type, lang_class: \
self.account_manager.account_handle_presence_subscribe(name,
from_jid,
stanza),
lambda name, from_jid, account_type, lang_class: \
[],
lambda name, from_jid, account_type, lang_class: \
self.account_manager.root_handle_presence_subscribe(from_jid,
stanza),
send_result=True)
return result return result
def handle_presence_subscribed(self, stanza): def handle_presence_subscribed(self, stanza):
@@ -541,18 +502,8 @@ class JCLComponent(Component, object):
"""Handle unsubscribe presence from user """Handle unsubscribe presence from user
""" """
self.__logger.debug("PRESENCE_UNSUBSCRIBE") self.__logger.debug("PRESENCE_UNSUBSCRIBE")
result = self.apply_registered_behavior(self.unsubscribe_handlers, stanza) result = self.apply_registered_behavior(self.presence_unsubscribe_handlers,
if result == []: stanza)
result = self.apply_behavior(\
stanza,
lambda name, from_jid, account_type, lang_class: \
self.account_manager.account_handle_presence_unsubscribe(name,
from_jid),
lambda name, from_jid, account_type, lang_class: \
[],
lambda name, from_jid, account_type, lang_class: \
[],
send_result=True)
return result return result
def handle_presence_unsubscribed(self, stanza): def handle_presence_unsubscribed(self, stanza):
@@ -570,7 +521,7 @@ class JCLComponent(Component, object):
Handle password response message Handle password response message
""" """
self.__logger.debug("MESSAGE: " + message.get_body()) self.__logger.debug("MESSAGE: " + message.get_body())
self.apply_registered_behavior(self.msg_handlers, message, False) self.apply_registered_behavior(self.msg_handlers, message)
return 1 return 1
def handle_command(self, info_query): def handle_command(self, info_query):
@@ -593,7 +544,7 @@ class JCLComponent(Component, object):
########################################################################### ###########################################################################
# Utils # Utils
########################################################################### ###########################################################################
def send_error_to_account(self, _account, exception): def send_error(self, _account, exception):
""" """ """ """
self.send_stanzas(self.account_manager.send_error_from_account(_account, exception)) self.send_stanzas(self.account_manager.send_error_from_account(_account, exception))
type, value, stack = sys.exc_info() type, value, stack = sys.exc_info()
@@ -643,72 +594,6 @@ class AccountManager(object):
account_classes = property(_get_account_classes, _set_account_classes) 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, node, name, category, type):
"""Implement discovery get_info on main component JID"""
self.__logger.debug("root_disco_get_info")
if not node:
disco_info = DiscoInfo()
disco_info.add_feature("jabber:iq:version")
disco_info.add_feature(command.COMMAND_NS)
if not self.has_multiple_account_type:
disco_info.add_feature("jabber:iq:register")
DiscoIdentity(disco_info, name,
category,
type)
return disco_info
else:
return None
###### disco_get_items handlers ######
def account_type_disco_get_items(self, from_jid, account_type):
"""Discovery get_items on an account type node"""
self.__logger.debug("Listing account for " + account_type)
account_class = self._get_account_class(account_type + "Account")
if account_class is not None:
return self._list_accounts(account_class,
from_jid.bare(),
account_type=account_type)
else:
self.__logger.error("Error: " + account_class.__name__
+ " class not in account_classes")
return None
def root_disco_get_items(self, node, from_jid, lang_class):
"""Discovery get_items on root node"""
if node is not None:
return None
disco_items = None
if self.has_multiple_account_type: # list accounts with only one type declared
disco_items = DiscoItems()
for account_type in self.account_types:
type_label_attr = "type_" + account_type.lower() + "_name"
if hasattr(lang_class, type_label_attr):
type_label = getattr(lang_class, type_label_attr)
else:
type_label = account_type
DiscoItem(disco_items,
JID(unicode(self.component.jid) + "/" +
account_type),
account_type,
type_label)
else:
disco_items = self._list_accounts(self.account_classes[0],
from_jid.bare())
return disco_items
###### get_register handlers ###### ###### get_register handlers ######
def account_get_register(self, info_query, def account_get_register(self, info_query,
name, name,
@@ -718,19 +603,19 @@ class AccountManager(object):
"""Handle get_register on an account. """Handle get_register on an account.
Return a preinitialized form""" Return a preinitialized form"""
info_query = info_query.make_result_response() info_query = info_query.make_result_response()
account_class = self._get_account_class(account_type + "Account") account_class = self.get_account_class(account_type + "Account")
self.db_connect() model.db_connect()
accounts = account_class.select(\ accounts = account_class.select(\
AND(account_class.q.name == name, AND(account_class.q.name == name,
account_class.q.user_jid == unicode(from_jid.bare()))) account_class.q.user_jid == unicode(from_jid.bare())))
if accounts is not None: if accounts is not None:
query = info_query.new_query("jabber:iq:register") query = info_query.new_query("jabber:iq:register")
_account = accounts[0] _account = accounts[0]
self.db_disconnect() model.db_disconnect()
self.get_reg_form_init(lang_class, self.get_reg_form_init(lang_class,
_account).as_xml(query) _account).as_xml(query)
else: else:
self.db_disconnect() model.db_disconnect()
return [info_query] return [info_query]
def _account_type_get_register(self, info_query, account_class, lang_class): def _account_type_get_register(self, info_query, account_class, lang_class):
@@ -748,7 +633,7 @@ class AccountManager(object):
"""Handle get_register on an account_type node""" """Handle get_register on an account_type node"""
return self._account_type_get_register(\ return self._account_type_get_register(\
info_query, info_query,
self._get_account_class(account_type + "Account"), self.get_account_class(account_type + "Account"),
lang_class) lang_class)
def root_get_register(self, info_query, lang_class): def root_get_register(self, info_query, lang_class):
@@ -762,7 +647,7 @@ class AccountManager(object):
def remove_all_accounts(self, user_jid): def remove_all_accounts(self, user_jid):
"""Unsubscribe all accounts associated to 'user_jid' then delete """Unsubscribe all accounts associated to 'user_jid' then delete
those accounts from the DataBase""" those accounts from the DataBase"""
self.db_connect() model.db_connect()
result = [] result = []
for _account in Account.select(Account.q.user_jid == unicode(user_jid)): for _account in Account.select(Account.q.user_jid == unicode(user_jid)):
self.__logger.debug("Deleting " + _account.name self.__logger.debug("Deleting " + _account.name
@@ -781,7 +666,7 @@ class AccountManager(object):
result.append(Presence(from_jid=self.component.jid, result.append(Presence(from_jid=self.component.jid,
to_jid=user_jid, to_jid=user_jid,
stanza_type="unsubscribed")) stanza_type="unsubscribed"))
self.db_disconnect() model.db_disconnect()
return result return result
def _populate_account(self, _account, lang_class, x_data, def _populate_account(self, _account, lang_class, x_data,
@@ -791,7 +676,7 @@ class AccountManager(object):
result = [] result = []
from_jid = info_query.get_from() from_jid = info_query.get_from()
bare_from_jid = unicode(from_jid.bare()) bare_from_jid = unicode(from_jid.bare())
self.db_connect() model.db_connect()
try: try:
for (field, field_type, field_options, field_post_func, for (field, field_type, field_options, field_post_func,
field_default_func) in _account.get_register_fields(): field_default_func) in _account.get_register_fields():
@@ -816,7 +701,7 @@ class AccountManager(object):
lang_class.mandatory_field % (field)) lang_class.mandatory_field % (field))
text.setNs(text.newNs(error.STANZA_ERROR_NS, None)) text.setNs(text.newNs(error.STANZA_ERROR_NS, None))
result.append(iq_error) result.append(iq_error)
self.db_disconnect() model.db_disconnect()
return result return result
result.append(info_query.make_result_response()) result.append(info_query.make_result_response())
@@ -840,7 +725,7 @@ class AccountManager(object):
to_jid=_account.user_jid, to_jid=_account.user_jid,
subject=_account.get_update_message_subject(lang_class), subject=_account.get_update_message_subject(lang_class),
body=_account.get_update_message_body(lang_class))) body=_account.get_update_message_body(lang_class)))
self.db_disconnect() model.db_disconnect()
return result return result
def account_set_register(self, name, from_jid, lang_class, def account_set_register(self, name, from_jid, lang_class,
@@ -848,13 +733,13 @@ class AccountManager(object):
"""Update account""" """Update account"""
self.__logger.debug("Updating account " + name) self.__logger.debug("Updating account " + name)
bare_from_jid = from_jid.bare() bare_from_jid = from_jid.bare()
self.db_connect() model.db_connect()
accounts = Account.select(\ accounts = Account.select(\
AND(Account.q.name == name, AND(Account.q.name == name,
Account.q.user_jid == unicode(bare_from_jid))) Account.q.user_jid == unicode(bare_from_jid)))
accounts_count = accounts.count() accounts_count = accounts.count()
_account = list(accounts)[0] _account = list(accounts)[0]
self.db_disconnect() model.db_disconnect()
if accounts_count > 1: if accounts_count > 1:
# Just print a warning, only the first account will be use # Just print a warning, only the first account will be use
self.__logger.error("There might not exist 2 accounts for " + self.__logger.error("There might not exist 2 accounts for " +
@@ -874,7 +759,7 @@ class AccountManager(object):
x_data, x_data,
info_query): info_query):
"""Create new account from account_class""" """Create new account from account_class"""
self.db_connect() model.db_connect()
bare_from_jid = from_jid.bare() bare_from_jid = from_jid.bare()
_account = account_class(user_jid=unicode(bare_from_jid), _account = account_class(user_jid=unicode(bare_from_jid),
name=name, name=name,
@@ -882,7 +767,7 @@ class AccountManager(object):
all_accounts = Account.select(\ all_accounts = Account.select(\
Account.q.user_jid == unicode(bare_from_jid)) Account.q.user_jid == unicode(bare_from_jid))
first_account = (all_accounts.count() > 0) first_account = (all_accounts.count() > 0)
self.db_disconnect() model.db_disconnect()
return self._populate_account(_account, lang_class, x_data, return self._populate_account(_account, lang_class, x_data,
info_query, True, first_account) info_query, True, first_account)
@@ -895,7 +780,7 @@ class AccountManager(object):
"""Create new typed account""" """Create new typed account"""
return self._account_type_set_register(\ return self._account_type_set_register(\
name, from_jid, name, from_jid,
self._get_account_class(account_type + "Account"), lang_class, self.get_account_class(account_type + "Account"), lang_class,
x_data, info_query) x_data, info_query)
def root_set_register(self, name, from_jid, lang_class, def root_set_register(self, name, from_jid, lang_class,
@@ -911,39 +796,12 @@ class AccountManager(object):
###### presence generic handlers ###### ###### presence generic handlers ######
def account_handle_presence(self, name, from_jid, presence_func):
"""Handle presence sent to an account JID"""
result = []
self.db_connect()
accounts = Account.select(\
AND(Account.q.name == name,
Account.q.user_jid == unicode(from_jid.bare())))
if accounts.count() > 0:
result.extend(presence_func(accounts[0]))
self.db_disconnect()
return result
def root_handle_presence(self, from_jid, presence_func, root_presence_func):
"""handle presence sent to component JID"""
result = []
self.db_connect()
accounts = Account.select(\
Account.q.user_jid == unicode(from_jid.bare()))
accounts_length = 0
for _account in accounts:
accounts_length += 1
result.extend(presence_func(_account))
self.db_disconnect()
if (accounts_length > 0):
result.extend(root_presence_func(accounts_length))
return result
def send_presence_all(self, presence): def send_presence_all(self, presence):
"""Send presence to all account. Optimized to use only one sql """Send presence to all account. Optimized to use only one sql
request""" request"""
current_user_jid = None current_user_jid = None
result = [] result = []
self.db_connect() model.db_connect()
# Explicit reference to account table (clauseTables) to use # Explicit reference to account table (clauseTables) to use
# "user_jid" column with Account subclasses # "user_jid" column with Account subclasses
for _account in \ for _account in \
@@ -951,146 +809,50 @@ class AccountManager(object):
orderBy="user_jid"): orderBy="user_jid"):
if current_user_jid != _account.user_jid: if current_user_jid != _account.user_jid:
current_user_jid = _account.user_jid current_user_jid = _account.user_jid
result.extend(self._send_presence(self.component.jid, result.extend(self.send_presence(self.component.jid,
_account.user_jid, _account.user_jid,
presence)) presence))
result.extend(getattr(self, "_send_presence_" + result.extend(getattr(self, "send_presence_" +
presence)(_account)) presence)(_account))
self.db_disconnect() model.db_disconnect()
return result return result
###### presence_available handlers ######
def account_handle_presence_available(self, name, from_jid, lang_class, show):
"""Handle presence \"available\" sent to an account JID"""
self.__logger.debug("Account handle presence available")
return self.account_handle_presence(\
name, from_jid,
lambda _account: \
self._send_presence_available(_account,
show,
lang_class))
def root_handle_presence_available(self, from_jid, lang_class, show):
"""Handle presence \"available\" sent to component JID"""
self.__logger.debug("Root handle presence available")
return self.root_handle_presence(\
from_jid,
lambda _account: \
self._send_presence_available(_account,
show,
lang_class),
lambda nb_accounts: \
self._send_root_presence(from_jid, "available", show,
str(nb_accounts) +
lang_class.message_status))
###### presence_unavailable handlers ######
def account_handle_presence_unavailable(self, name, from_jid):
"""Handle presence \"unavailable\" sent to an account JID"""
self.__logger.debug("Account handle presence available")
return self.account_handle_presence(\
name, from_jid,
lambda _account: \
self._send_presence_unavailable(_account))
def root_handle_presence_unavailable(self, from_jid):
"""Handle presence \"unavailable\" sent to component JID"""
self.__logger.debug("Root handle presence available")
return self.root_handle_presence(\
from_jid,
lambda _account: \
self._send_presence_unavailable(_account),
lambda nb_accounts: \
self._send_root_presence(from_jid, "unavailable"))
###### presence_subscribe handlers ######
def account_handle_presence_subscribe(self, name, from_jid, stanza):
"""Handle \"subscribe\" iq sent to an account JID"""
if self._has_account(from_jid, name):
return [stanza.make_accept_response()]
else:
self.__logger.debug("Account '" + str(name) + "' for user '" +
unicode(from_jid.bare()) + "' was not found. " +
"Refusing subscription")
return []
def root_handle_presence_subscribe(self, from_jid, stanza):
"""Handle \"subscribe\" iq sent to component JID"""
if self._has_account(from_jid):
return [stanza.make_accept_response()]
else:
self.__logger.debug("User '" +
unicode(from_jid.bare()) +
"' doesn't have any account. " +
"Refusing subscription")
return []
###### presence_unsubscribe handler (only apply to account) ######
def account_handle_presence_unsubscribe(self, name, from_jid):
"""Handle \"unsubscribe\" iq sent to account JID"""
result = []
self.db_connect()
accounts = Account.select(\
AND(Account.q.name == name,
Account.q.user_jid == unicode(from_jid.bare())))
for _account in accounts:
result.append(Presence(from_jid=_account.jid,
to_jid=from_jid,
stanza_type="unsubscribe"))
result.append(Presence(from_jid=_account.jid,
to_jid=from_jid,
stanza_type="unsubscribed"))
_account.destroySelf()
self.db_disconnect()
return result
def probe_all_accounts_presence(self): def probe_all_accounts_presence(self):
"""Send presence probe to all registered accounts""" """Send presence probe to all registered accounts"""
return self.send_presence_all("probe") return self.send_presence_all("probe")
###### Utils methods ###### ###### Utils methods ######
def _has_account(self, from_jid, name=None): def list_accounts(self, bare_from_jid, account_class=None,
"""Check if user with \"from_jid\" JID has an account. account_type=""):
Check if an account named \"name\" exist if \"name\" given."""
self.db_connect()
if name is None:
accounts = Account.select(\
Account.q.user_jid == unicode(from_jid.bare()))
else:
accounts = Account.select(\
AND(Account.q.name == name,
Account.q.user_jid == unicode(from_jid.bare())))
result = (accounts is not None \
and accounts.count() > 0)
self.db_disconnect()
return result
def _list_accounts(self, _account_class, bare_from_jid,
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_class is None:
account_class = self.account_classes[0]
if account_type is not None and account_type != "": if account_type is not None and account_type != "":
resource = "/" + account_type resource = "/" + account_type
account_type = account_type + "/" account_type = account_type + "/"
else: else:
resource = "" resource = ""
self.db_connect() model.db_connect()
accounts = _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: if accounts.count() == 0:
return None return
disco_items = DiscoItems()
for _account in accounts: for _account in accounts:
self.__logger.debug(str(_account)) yield (_account, resource, account_type)
DiscoItem(disco_items, model.db_disconnect()
JID(unicode(_account.jid) + resource),
account_type + _account.name,
_account.long_name)
self.db_disconnect()
return disco_items
def _get_account_class(self, account_class_name): def list_account_types(self, lang_class):
"""List account supported types"""
for account_type in self.account_types:
type_label_attr = "type_" + account_type.lower() + "_name"
if hasattr(lang_class, type_label_attr):
type_label = getattr(lang_class, type_label_attr)
else:
type_label = account_type
yield (account_type, type_label)
def get_account_class(self, account_class_name):
"""Return account class definition from declared classes in """Return account class definition from declared classes in
account_classes from its class name""" account_classes from its class name"""
self.__logger.debug("Looking for " + account_class_name) self.__logger.debug("Looking for " + account_class_name)
@@ -1101,19 +863,9 @@ class AccountManager(object):
self.__logger.debug(account_class_name + " not found") self.__logger.debug(account_class_name + " not found")
return None 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
def get_reg_form(self, lang_class, _account_class, bare_from_jid): def get_reg_form(self, lang_class, _account_class, bare_from_jid):
"""Return register form based on language and account class """
Return register form based on language and account class
""" """
reg_form = Form(title=lang_class.register_title, reg_form = Form(title=lang_class.register_title,
instructions=lang_class.register_instructions) instructions=lang_class.register_instructions)
@@ -1123,7 +875,7 @@ class AccountManager(object):
name="name", name="name",
required=True) required=True)
self.db_connect() model.db_connect()
for (field_name, for (field_name,
field_type, field_type,
field_options, field_options,
@@ -1158,11 +910,12 @@ class AccountManager(object):
except: except:
self.__logger.debug("Setting field " + field_name + " required") self.__logger.debug("Setting field " + field_name + " required")
field.required = True field.required = True
self.db_disconnect() model.db_disconnect()
return reg_form return reg_form
def get_reg_form_init(self, lang_class, _account): def get_reg_form_init(self, lang_class, _account):
"""Return register form for an existing account (update) """
Return register form for an existing account (update)
""" """
reg_form = self.get_reg_form(lang_class, _account.__class__, reg_form = self.get_reg_form(lang_class, _account.__class__,
_account.user_jid) _account.user_jid)
@@ -1177,31 +930,34 @@ class AccountManager(object):
"""Compose account jid from account name""" """Compose account jid from account name"""
return name + u"@" + unicode(self.component.jid) return name + u"@" + unicode(self.component.jid)
def _send_presence_probe(self, _account): def send_presence_probe(self, _account):
"""Send presence probe to account's user""" """Send presence probe to account's user"""
return [Presence(from_jid=_account.jid, return [Presence(from_jid=_account.jid,
to_jid=_account.user_jid, to_jid=_account.user_jid,
stanza_type="probe")] stanza_type="probe")]
def _send_presence_unavailable(self, _account): def send_presence_unavailable(self, _account):
"""Send unavailable presence to account's user""" """Send unavailable presence to account's user"""
model.db_connect()
_account.status = account.OFFLINE _account.status = account.OFFLINE
return [Presence(from_jid=_account.jid, result = [Presence(from_jid=_account.jid,
to_jid=_account.user_jid, to_jid=_account.user_jid,
stanza_type="unavailable")] stanza_type="unavailable")]
model.db_disconnect()
def _send_root_presence(self, to_jid, presence_type,
show=None, status=None):
result = self._send_presence(self.component.jid, to_jid,
presence_type, show=show,
status=status)
result.extend(self._send_root_presence_legacy(to_jid,
presence_type,
show=show,
status=status))
return result return result
def _send_presence(self, from_jid, to_jid, presence_type, status=None, show=None): def send_root_presence(self, to_jid, presence_type,
show=None, status=None):
result = self.send_presence(self.component.jid, to_jid,
presence_type, show=show,
status=status)
result.extend(self.send_root_presence_legacy(to_jid,
presence_type,
show=show,
status=status))
return result
def send_presence(self, from_jid, to_jid, presence_type, status=None, show=None):
"""Send presence stanza""" """Send presence stanza"""
return [Presence(from_jid=from_jid, return [Presence(from_jid=from_jid,
to_jid=to_jid, to_jid=to_jid,
@@ -1209,10 +965,11 @@ class AccountManager(object):
show=show, show=show,
stanza_type=presence_type)] stanza_type=presence_type)]
def _send_presence_available(self, _account, show, lang_class): def send_presence_available(self, _account, show, lang_class):
"""Send available presence to account's user and ask for password """Send available presence to account's user and ask for password
if necessary""" if necessary"""
result = [] result = []
model.db_connect()
_account.default_lang_class = lang_class _account.default_lang_class = lang_class
old_status = _account.status old_status = _account.status
if show is None: if show is None:
@@ -1225,28 +982,24 @@ class AccountManager(object):
show=show, show=show,
stanza_type="available")) stanza_type="available"))
if hasattr(_account, 'store_password') \ if hasattr(_account, 'store_password') \
and hasattr(_account, 'password') \ and hasattr(_account, 'password') \
and _account.store_password == False \ and _account.store_password == False \
and old_status == account.OFFLINE \ and old_status == account.OFFLINE \
and _account.password == None : and _account.password == None :
result.extend(self.ask_password(_account, lang_class)) result.extend(self.ask_password(_account, lang_class))
model.db_disconnect()
return result return result
def _send_root_presence_legacy(self, to_jid, presence_type, def send_root_presence_legacy(self, to_jid, presence_type,
status=None, show=None): status=None, show=None):
"""Send presence from legacy JID""" """Send presence from legacy JID"""
result = [] result = []
self.db_connect() for legacy_jid in account.get_legacy_jids(unicode(to_jid.bare())):
legacy_jids = LegacyJID.select(\
AND(LegacyJID.q.accountID == Account.q.id,
Account.q.user_jid == unicode(to_jid.bare())))
for legacy_jid in legacy_jids:
result.append(Presence(from_jid=legacy_jid.jid, result.append(Presence(from_jid=legacy_jid.jid,
to_jid=to_jid, to_jid=to_jid,
show=show, show=show,
status=status, status=status,
stanza_type=presence_type)) stanza_type=presence_type))
self.db_disconnect()
return result return result
def ask_password(self, _account, lang_class): def ask_password(self, _account, lang_class):
@@ -1254,8 +1007,8 @@ class AccountManager(object):
""" """
result = [] result = []
if hasattr(_account, 'waiting_password_reply') \ if hasattr(_account, 'waiting_password_reply') \
and not _account.waiting_password_reply \ and not _account.waiting_password_reply \
and _account.status != account.OFFLINE: and _account.status != account.OFFLINE:
_account.waiting_password_reply = True _account.waiting_password_reply = True
result.append(Message(from_jid=_account.jid, result.append(Message(from_jid=_account.jid,
to_jid=_account.user_jid, to_jid=_account.user_jid,
@@ -1265,6 +1018,20 @@ class AccountManager(object):
(_account.name))) (_account.name)))
return result return result
def set_password(self, _account, from_jid, password, lang_class):
"""
Set password to given account
"""
model.db_connect()
_account.password = password
_account.waiting_password_reply = False
result = [Message(from_jid=_account.jid,
to_jid=from_jid,
subject=lang_class.password_saved_for_session,
body=lang_class.password_saved_for_session)]
model.db_disconnect()
return result
def send_error_from_account(self, _account, exception): def send_error_from_account(self, _account, exception):
"""Send an error message only one time until _account.in_error """Send an error message only one time until _account.in_error
has been reset to False""" has been reset to False"""

149
src/jcl/jabber/disco.py Normal file
View File

@@ -0,0 +1,149 @@
##
## disco.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Wed Jun 27 22:27:25 2007 David Rousselie
## $Id$
##
## Copyright (C) 2007 David Rousselie
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 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 General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import logging
from pyxmpp.jid import JID
from pyxmpp.jabber.disco import DiscoInfo, DiscoItems, DiscoItem, DiscoIdentity
import jcl.jabber as jabber
class DiscoHandler(object):
"""Handle disco get items requests"""
def __init__(self, component):
self.component = component
def filter(self, stanza, lang_class, node):
"""Filter requests to be handled"""
return False
def handle(self, stanza, lang_class, node, disco_obj, data):
"""Handle disco get items request"""
return None
class RootDiscoGetInfoHandler(DiscoHandler):
filter = jabber.root_filter
def __init__(self, component):
DiscoHandler.__init__(self, component)
self.__logger = logging.getLogger("jcl.jabber.RootDiscoGetInfoHandler")
def handle(self, stanza, lang_class, node, disco_obj, data):
"""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.component.account_manager.has_multiple_account_type:
disco_info.add_feature("jabber:iq:register")
DiscoIdentity(disco_info, self.component.name,
self.component.disco_identity.category,
self.component.disco_identity.type)
return [disco_info]
class AccountDiscoGetInfoHandler(DiscoHandler):
filter = jabber.account_filter
def __init__(self, component):
DiscoHandler.__init__(self, component)
self.__logger = logging.getLogger("jcl.jabber.AccountDiscoGetInfoHandler")
def handle(self, stanza, lang_class, node, disco_obj, data):
"""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]
class AccountTypeDiscoGetInfoHandler(AccountDiscoGetInfoHandler):
filter = jabber.account_type_filter
def __init__(self, component):
AccountDiscoGetInfoHandler.__init__(self, component)
self.__logger = logging.getLogger("jcl.jabber.AccountTypeDiscoGetInfoHandler")
class RootDiscoGetItemsHandler(DiscoHandler):
filter = jabber.root_filter
def __init__(self, component):
DiscoHandler.__init__(self, component)
self.__logger = logging.getLogger("jcl.jabber.RootDiscoGetItemsHandler")
def handle(self, stanza, lang_class, node, disco_obj, data):
"""Discovery get_items on root node"""
from_jid = stanza.get_from()
if node is not None:
return None
disco_items = None
if self.component.account_manager.has_multiple_account_type: # list accounts with only one type declared
disco_items = DiscoItems()
for (account_type, type_label) in \
self.component.account_manager.list_account_types(lang_class):
DiscoItem(disco_items,
JID(unicode(self.component.jid) + "/" +
account_type),
account_type,
type_label)
else:
disco_items = DiscoItems()
for (_account, resource, account_type) in \
self.component.account_manager.list_accounts(from_jid.bare()):
DiscoItem(disco_items,
JID(unicode(_account.jid) + resource),
account_type + _account.name,
_account.long_name)
return [disco_items]
class AccountTypeDiscoGetItemsHandler(DiscoHandler):
filter = jabber.account_type_filter
def __init__(self, component):
DiscoHandler.__init__(self, component)
self.__logger = logging.getLogger("jcl.jabber.AccountTypeDiscoGetItemsHandler")
def handle(self, stanza, lang_class, node, disco_obj, data):
"""Discovery get_items on an account type node"""
account_type = data
from_jid = stanza.get_from()
self.__logger.debug("Listing account for " + account_type)
account_class = self.component.account_manager.get_account_class(account_type + "Account")
if account_class is not None:
disco_items = DiscoItems()
for (_account, resource, account_type) in \
self.component.account_manager.list_accounts(from_jid.bare(),
account_class,
account_type=account_type):
DiscoItem(disco_items,
JID(unicode(_account.jid) + resource),
account_type + _account.name,
_account.long_name)
return [disco_items]
else:
self.__logger.error("Error: " + account_class.__name__
+ " class not in account_classes")
return []

View File

@@ -31,6 +31,7 @@ import logging
from jcl.jabber import Handler from jcl.jabber import Handler
from jcl.jabber.component import JCLComponent from jcl.jabber.component import JCLComponent
from jcl.lang import Lang from jcl.lang import Lang
import jcl.model as model
from jcl.model.account import Account from jcl.model.account import Account
from pyxmpp.message import Message from pyxmpp.message import Message
@@ -48,14 +49,12 @@ class FeederComponent(JCLComponent):
secret, secret,
server, server,
port, port,
db_connection_str,
lang = Lang()): lang = Lang()):
JCLComponent.__init__(self, JCLComponent.__init__(self,
jid, jid,
secret, secret,
server, server,
port, port,
db_connection_str,
lang=lang) lang=lang)
# Define default feeder and sender, can be override # Define default feeder and sender, can be override
self.handler = FeederHandler(Feeder(self), Sender(self)) self.handler = FeederHandler(Feeder(self), Sender(self))
@@ -65,12 +64,12 @@ class FeederComponent(JCLComponent):
def handle_tick(self): def handle_tick(self):
"""Implement main feed/send behavior""" """Implement main feed/send behavior"""
self.db_connect() model.db_connect()
self.handler.handle(\ self.handler.handle(\
None, self.lang.get_default_lang_class(), None, self.lang.get_default_lang_class(),
self.handler.filter(None, self.handler.filter(None,
self.lang.get_default_lang_class())) self.lang.get_default_lang_class()))
self.db_disconnect() model.db_disconnect()

View File

@@ -22,16 +22,19 @@
import re import re
from sqlobject.sqlbuilder import AND
from pyxmpp.message import Message from pyxmpp.message import Message
from jcl.jabber import Handler from jcl.jabber import Handler
import jcl.model.account as account
from jcl.model.account import Account from jcl.model.account import Account
class PasswordMessageHandler(Handler): class PasswordMessageHandler(Handler):
"""Handle password message""" """Handle password message"""
def __init__(self): def __init__(self, component):
"""handler constructor""" """handler constructor"""
Handler.__init__(self, component)
self.password_regexp = re.compile("\[PASSWORD\]") self.password_regexp = re.compile("\[PASSWORD\]")
def filter(self, stanza, lang_class): def filter(self, stanza, lang_class):
@@ -39,23 +42,14 @@ class PasswordMessageHandler(Handler):
Return the uniq account associated with a name and user JID. Return the uniq account associated with a name and user JID.
DB connection might already be opened. DB connection might already be opened.
""" """
name = stanza.get_to().node _account = account.get_account(stanza.get_to().node,
bare_from_jid = unicode(stanza.get_from().bare()) stanza.get_from().bare())
accounts = Account.select(\
AND(Account.q.name == name,
Account.q.user_jid == bare_from_jid))
if accounts.count() > 1:
print >>sys.stderr, "Account " + name + " for user " + \
bare_from_jid + " must be uniq"
elif accounts.count() == 0:
return None
_account = accounts[0]
if hasattr(_account, 'password') \ if hasattr(_account, 'password') \
and hasattr(_account, 'waiting_password_reply') \ and hasattr(_account, 'waiting_password_reply') \
and (getattr(_account, 'waiting_password_reply') == True) \ and (getattr(_account, 'waiting_password_reply') == True) \
and self.password_regexp.search(stanza.get_subject()) \ and self.password_regexp.search(stanza.get_subject()) \
is not None: is not None:
return accounts return _account
else: else:
return None return None
@@ -63,10 +57,6 @@ class PasswordMessageHandler(Handler):
""" """
Receive password in stanza (must be a Message) for given account. Receive password in stanza (must be a Message) for given account.
""" """
_account = data[0] return self.component.account_manager.set_password(data, stanza.get_from(),
_account.password = stanza.get_body() stanza.get_body(),
_account.waiting_password_reply = False lang_class)
return [Message(from_jid=_account.jid,
to_jid=stanza.get_from(),
subject=lang_class.password_saved_for_session,
body=lang_class.password_saved_for_session)]

View File

@@ -24,17 +24,19 @@
from pyxmpp.presence import Presence from pyxmpp.presence import Presence
from jcl.jabber import Handler from jcl.jabber import Handler
import jcl.jabber as jabber
import jcl.model as model
class DefaultPresenceHandler(Handler): class DefaultPresenceHandler(Handler):
"""Handle presence""" """Handle presence"""
def handle(self, presence, lang_class, data): def handle(self, stanza, lang_class, data):
"""Return same presence as receive one""" """Return same presence as receive one"""
to_jid = presence.get_to() to_jid = stanza.get_to()
from_jid = presence.get_from() from_jid = stanza.get_from()
presence.set_to(from_jid) stanza.set_to(from_jid)
presence.set_from(to_jid) stanza.set_from(to_jid)
return [presence] return [stanza]
class DefaultSubscribeHandler(Handler): class DefaultSubscribeHandler(Handler):
"""Return default response to subscribe queries""" """Return default response to subscribe queries"""
@@ -64,3 +66,94 @@ class DefaultUnsubscribeHandler(Handler):
stanza_type="unsubscribed")) stanza_type="unsubscribed"))
return result return result
class AccountPresenceHandler(Handler):
filter = jabber.get_account_filter
def handle(self, stanza, lang_class, data):
"""Handle presence sent to an account JID"""
result = []
_account = data
result.extend(self.get_account_presence(stanza, lang_class, _account))
return result
class AccountPresenceAvailableHandler(AccountPresenceHandler):
def get_account_presence(self, stanza, lang_class, _account):
return self.component.account_manager.send_presence_available(_account,
stanza.get_show(),
lang_class)
class RootPresenceHandler(Handler):
filter = jabber.get_accounts_root_filter
def handle(self, stanza, lang_class, data):
"""handle presence sent to component JID"""
result = []
accounts_length = 0
accounts = data
for _account in accounts:
accounts_length += 1
result.extend(self.get_account_presence(stanza, lang_class, _account))
if accounts_length > 0:
result.extend(self.get_root_presence(stanza, lang_class, accounts_length))
return result
class RootPresenceAvailableHandler(RootPresenceHandler, AccountPresenceAvailableHandler):
def get_root_presence(self, stanza, lang_class, nb_accounts):
return self.component.account_manager.send_root_presence(stanza.get_from(),
"available",
stanza.get_show(),
str(nb_accounts) +
lang_class.message_status)
class AccountPresenceUnavailableHandler(AccountPresenceHandler):
def get_account_presence(self, stanza, lang_class, _account):
return self.component.account_manager.send_presence_unavailable(_account)
class RootPresenceUnavailableHandler(RootPresenceHandler, AccountPresenceUnavailableHandler):
def get_root_presence(self, stanza, lang_class, nb_accounts):
return self.component.account_manager.send_root_presence(stanza.get_from(),
"unavailable")
class AccountPresenceSubscribeHandler(Handler):
""""""
filter = jabber.get_account_filter
def handle(self, stanza, lang_class, data):
"""Handle \"subscribe\" iq sent to an account JID"""
return [stanza.make_accept_response()]
class RootPresenceSubscribeHandler(AccountPresenceSubscribeHandler):
""""""
filter = jabber.get_accounts_root_filter
def handle(self, stanza, lang_class, data):
"""Handle \"subscribe\" iq sent to component JID"""
if list(data) != []:
return AccountPresenceSubscribeHandler.handle(self, stanza,
lang_class, None)
else:
return None
class AccountPresenceUnsubscribeHandler(Handler):
""""""
filter = jabber.get_account_filter
def handle(self, stanza, lang_class, data):
"""Handle \"unsubscribe\" iq sent to account JID"""
result = []
from_jid = stanza.get_from()
_account = data
model.db_connect()
result.append(Presence(from_jid=_account.jid,
to_jid=from_jid,
stanza_type="unsubscribe"))
result.append(Presence(from_jid=_account.jid,
to_jid=from_jid,
stanza_type="unsubscribed"))
_account.destroySelf()
model.db_disconnect()
return result

View File

@@ -3,13 +3,15 @@ __revision__ = ""
import unittest import unittest
from jcl.jabber.tests import component, feeder, command from jcl.jabber.tests import component, feeder, command, message, presence
def suite(): def suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(component.suite()) suite.addTest(component.suite())
suite.addTest(feeder.suite()) suite.addTest(feeder.suite())
suite.addTest(command.suite()) suite.addTest(command.suite())
suite.addTest(message.suite())
suite.addTest(presence.suite())
return suite return suite
if __name__ == '__main__': if __name__ == '__main__':

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,7 @@ from jcl.jabber.component import JCLComponent
from jcl.jabber.feeder import FeederComponent, Feeder, Sender, MessageSender, \ from jcl.jabber.feeder import FeederComponent, Feeder, Sender, MessageSender, \
HeadlineSender, FeederHandler HeadlineSender, FeederHandler
from jcl.model.account import Account, LegacyJID from jcl.model.account import Account, LegacyJID
import jcl.model as model
from jcl.model import account from jcl.model import account
from jcl.model.tests.account import ExampleAccount, Example2Account from jcl.model.tests.account import ExampleAccount, Example2Account
@@ -65,24 +66,24 @@ class FeederComponent_TestCase(JCLComponent_TestCase):
self.comp = FeederComponent("jcl.test.com", self.comp = FeederComponent("jcl.test.com",
"password", "password",
"localhost", "localhost",
"5347", "5347")
'sqlite://' + DB_URL) model.db_connection_str = 'sqlite://' + DB_URL
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) model.db_connect()
Account.createTable(ifNotExists = True) Account.createTable(ifNotExists = True)
LegacyJID.createTable(ifNotExists=True) LegacyJID.createTable(ifNotExists=True)
ExampleAccount.createTable(ifNotExists = True) ExampleAccount.createTable(ifNotExists = True)
Example2Account.createTable(ifNotExists = True) Example2Account.createTable(ifNotExists = True)
del account.hub.threadConnection model.db_disconnect()
def tearDown(self): def tearDown(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) model.db_connect()
Account.dropTable(ifExists = True) Account.dropTable(ifExists = True)
LegacyJID.dropTable(ifExists=True) LegacyJID.dropTable(ifExists=True)
ExampleAccount.dropTable(ifExists = True) ExampleAccount.dropTable(ifExists = True)
Example2Account.dropTable(ifExists = True) Example2Account.dropTable(ifExists = True)
del TheURIOpener.cachedURIs['sqlite://' + DB_URL] del TheURIOpener.cachedURIs['sqlite://' + DB_URL]
account.hub.threadConnection.close() model.hub.threadConnection.close()
del account.hub.threadConnection model.db_disconnect()
if os.path.exists(DB_PATH): if os.path.exists(DB_PATH):
os.unlink(DB_PATH) os.unlink(DB_PATH)
@@ -90,8 +91,8 @@ class FeederComponent_TestCase(JCLComponent_TestCase):
self.comp.time_unit = 1 self.comp.time_unit = 1
self.comp.stream = MockStream() self.comp.stream = MockStream()
self.comp.stream_class = MockStream self.comp.stream_class = MockStream
run_thread = threading.Thread(target = self.comp.run, \ run_thread = threading.Thread(target=self.comp.run,
name = "run_thread") name="run_thread")
run_thread.start() run_thread.start()
time.sleep(1) time.sleep(1)
self.comp.running = False self.comp.running = False
@@ -118,7 +119,7 @@ class FeederComponent_TestCase(JCLComponent_TestCase):
self.comp.stream = MockStream() self.comp.stream = MockStream()
self.comp.stream_class = MockStream self.comp.stream_class = MockStream
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) model.db_connect()
account11 = Account(user_jid = "user1@test.com", \ account11 = Account(user_jid = "user1@test.com", \
name = "account11", \ name = "account11", \
jid = "account11@jcl.test.com") jid = "account11@jcl.test.com")
@@ -190,30 +191,30 @@ class MessageSender_TestCase(unittest.TestCase):
self.comp = FeederComponent("jcl.test.com", self.comp = FeederComponent("jcl.test.com",
"password", "password",
"localhost", "localhost",
"5347", "5347")
'sqlite://' + DB_URL) model.db_connection_str = 'sqlite://' + DB_URL
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) model.db_connect()
Account.createTable(ifNotExists = True) Account.createTable(ifNotExists = True)
del account.hub.threadConnection model.db_disconnect()
self.sender = MessageSender(self.comp) self.sender = MessageSender(self.comp)
self.message_type = None self.message_type = None
def tearDown(self): def tearDown(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) model.db_connect()
Account.dropTable(ifExists = True) Account.dropTable(ifExists = True)
del TheURIOpener.cachedURIs['sqlite://' + DB_URL] del TheURIOpener.cachedURIs['sqlite://' + DB_URL]
account.hub.threadConnection.close() model.hub.threadConnection.close()
del account.hub.threadConnection model.db_disconnect()
if os.path.exists(DB_PATH): if os.path.exists(DB_PATH):
os.unlink(DB_PATH) os.unlink(DB_PATH)
def test_send(self): def test_send(self):
self.comp.stream = MockStream() self.comp.stream = MockStream()
self.comp.stream_class = MockStream self.comp.stream_class = MockStream
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) model.db_connect()
account11 = Account(user_jid = "user1@test.com", \ account11 = Account(user_jid="user1@test.com",
name = "account11", \ name="account11",
jid = "account11@jcl.test.com") jid="account11@jcl.test.com")
self.sender.send(account11, ("subject", "Body message")) self.sender.send(account11, ("subject", "Body message"))
self.assertEquals(len(self.comp.stream.sent), 1) self.assertEquals(len(self.comp.stream.sent), 1)
message = self.comp.stream.sent[0] message = self.comp.stream.sent[0]
@@ -222,7 +223,7 @@ class MessageSender_TestCase(unittest.TestCase):
self.assertEquals(message.get_subject(), "subject") self.assertEquals(message.get_subject(), "subject")
self.assertEquals(message.get_body(), "Body message") self.assertEquals(message.get_body(), "Body message")
self.assertEquals(message.get_type(), self.message_type) self.assertEquals(message.get_type(), self.message_type)
del account.hub.threadConnection model.db_disconnect()
class HeadlineSender_TestCase(MessageSender_TestCase): class HeadlineSender_TestCase(MessageSender_TestCase):
def setUp(self): def setUp(self):
@@ -235,24 +236,25 @@ class FeederHandler_TestCase(unittest.TestCase):
self.handler = FeederHandler(FeederMock(), SenderMock()) self.handler = FeederHandler(FeederMock(), SenderMock())
if os.path.exists(DB_PATH): if os.path.exists(DB_PATH):
os.unlink(DB_PATH) os.unlink(DB_PATH)
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) model.db_connection_str = 'sqlite://' + DB_URL
model.db_connect()
Account.createTable(ifNotExists = True) Account.createTable(ifNotExists = True)
ExampleAccount.createTable(ifNotExists = True) ExampleAccount.createTable(ifNotExists = True)
del account.hub.threadConnection model.db_disconnect()
def tearDown(self): def tearDown(self):
self.handler = None self.handler = None
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) model.db_connect()
ExampleAccount.dropTable(ifExists = True) ExampleAccount.dropTable(ifExists = True)
Account.dropTable(ifExists = True) Account.dropTable(ifExists = True)
del TheURIOpener.cachedURIs['sqlite://' + DB_URL] del TheURIOpener.cachedURIs['sqlite://' + DB_URL]
account.hub.threadConnection.close() model.hub.threadConnection.close()
del account.hub.threadConnection model.db_disconnect()
if os.path.exists(DB_PATH): if os.path.exists(DB_PATH):
os.unlink(DB_PATH) os.unlink(DB_PATH)
def test_filter(self): def test_filter(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) model.db_connect()
account12 = ExampleAccount(user_jid="user2@test.com", account12 = ExampleAccount(user_jid="user2@test.com",
name="account12", name="account12",
jid="account12@jcl.test.com") jid="account12@jcl.test.com")
@@ -264,10 +266,10 @@ class FeederHandler_TestCase(unittest.TestCase):
# accounts must be ordered by user_jid # accounts must be ordered by user_jid
self.assertEquals(accounts[0].name, "account11") self.assertEquals(accounts[0].name, "account11")
self.assertEquals(accounts[1].name, "account12") self.assertEquals(accounts[1].name, "account12")
del account.hub.threadConnection model.db_disconnect()
def test_handle(self): def test_handle(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) model.db_connect()
account11 = ExampleAccount(user_jid="user1@test.com", account11 = ExampleAccount(user_jid="user1@test.com",
name="account11", name="account11",
jid="account11@jcl.test.com") jid="account11@jcl.test.com")
@@ -279,7 +281,7 @@ class FeederHandler_TestCase(unittest.TestCase):
self.assertEquals(len(sent), 2) self.assertEquals(len(sent), 2)
self.assertEquals(sent[0], (account11, ("subject", "body"))) self.assertEquals(sent[0], (account11, ("subject", "body")))
self.assertEquals(sent[1], (account12, ("subject", "body"))) self.assertEquals(sent[1], (account12, ("subject", "body")))
del account.hub.threadConnection model.db_disconnect()
def suite(): def suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()

View File

@@ -0,0 +1,161 @@
##
## message.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Fri Jul 6 21:40:55 2007 David Rousselie
## $Id$
##
## Copyright (C) 2007 David Rousselie
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 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 General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import unittest
import sys
import os
from pyxmpp.message import Message
from sqlobject.dbconnection import TheURIOpener
from jcl.lang import Lang
from jcl.jabber.component import JCLComponent
from jcl.jabber.message import PasswordMessageHandler
import jcl.model.account as account
import jcl.model as model
from jcl.model.account import Account
from jcl.model.tests.account import ExampleAccount
if sys.platform == "win32":
DB_PATH = "/c|/temp/jcl_test.db"
else:
DB_PATH = "/tmp/jcl_test.db"
DB_URL = DB_PATH# + "?debug=1&debugThreading=1"
class PasswordMessageHandler_TestCase(unittest.TestCase):
def setUp(self):
self.comp = JCLComponent("jcl.test.com",
"password",
"localhost",
"5347",
'sqlite://' + DB_URL)
self.handler = PasswordMessageHandler(self.comp)
if os.path.exists(DB_PATH):
os.unlink(DB_PATH)
model.db_connection_str = 'sqlite://' + DB_URL
model.db_connect()
Account.createTable(ifNotExists = True)
ExampleAccount.createTable(ifNotExists = True)
model.db_disconnect()
def tearDown(self):
model.db_connect()
ExampleAccount.dropTable(ifExists = True)
Account.dropTable(ifExists = True)
del TheURIOpener.cachedURIs['sqlite://' + DB_URL]
model.hub.threadConnection.close()
model.db_disconnect()
if os.path.exists(DB_PATH):
os.unlink(DB_PATH)
def test_filter_waiting_password(self):
model.db_connect()
account11 = ExampleAccount(user_jid="user1@test.com",
name="account11",
jid="account11@jcl.test.com")
account11.waiting_password_reply = True
account12 = ExampleAccount(user_jid="user1@test.com",
name="account12",
jid="account12@jcl.test.com")
message = Message(from_jid="user1@test.com",
to_jid="account11@jcl.test.com",
subject="[PASSWORD]",
body="secret")
_account = self.handler.filter(message, None)
self.assertEquals(_account.name, "account11")
model.db_disconnect()
def test_filter_not_waiting_password(self):
model.db_connect()
account11 = ExampleAccount(user_jid="user1@test.com",
name="account11",
jid="account11@jcl.test.com")
account11.waiting_password_reply = False
account12 = ExampleAccount(user_jid="user1@test.com",
name="account12",
jid="account12@jcl.test.com")
message = Message(from_jid="user1@test.com",
to_jid="account11@jcl.test.com",
subject="[PASSWORD]",
body="secret")
_account = self.handler.filter(message, None)
self.assertEquals(_account, None)
model.db_disconnect()
def test_filter_not_good_message(self):
model.db_connect()
account11 = ExampleAccount(user_jid="user1@test.com",
name="account11",
jid="account11@jcl.test.com")
account11.waiting_password_reply = True
account12 = ExampleAccount(user_jid="user1@test.com",
name="account12",
jid="account12@jcl.test.com")
message = Message(from_jid="user1@test.com",
to_jid="account11@jcl.test.com",
subject="[WRONG MESSAGE]",
body="secret")
_account = self.handler.filter(message, None)
self.assertEquals(_account, None)
model.db_disconnect()
def test_filter_not_password_account(self):
model.db_connect()
account11 = Account(user_jid="user1@test.com",
name="account11",
jid="account11@jcl.test.com")
account12 = Account(user_jid="user1@test.com",
name="account12",
jid="account12@jcl.test.com")
message = Message(from_jid="user1@test.com",
to_jid="account11@jcl.test.com",
subject="[PASSWORD]",
body="secret")
_account = self.handler.filter(message, None)
self.assertEquals(_account, None)
model.db_disconnect()
def test_handle(self):
model.db_connect()
account11 = ExampleAccount(user_jid="user1@test.com",
name="account11",
jid="account11@jcl.test.com")
account12 = ExampleAccount(user_jid="user1@test.com",
name="account12",
jid="account12@jcl.test.com")
message = Message(from_jid="user1@test.com",
to_jid="account11@jcl.test.com",
subject="[PASSWORD]",
body="secret")
messages = self.handler.handle(message, Lang.en, account11)
self.assertEquals(len(messages), 1)
self.assertEquals(account11.password, "secret")
model.db_disconnect()
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(PasswordMessageHandler_TestCase, 'test'))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')

View File

@@ -0,0 +1,98 @@
##
## presence.py
## Login : David Rousselie <dax@happycoders.org>
## Started on Fri Jul 6 21:45:43 2007 David Rousselie
## $Id$
##
## Copyright (C) 2007 David Rousselie
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 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 General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import unittest
from pyxmpp.presence import Presence
from jcl.jabber.presence import DefaultSubscribeHandler, \
DefaultUnsubscribeHandler, DefaultPresenceHandler
class DefaultSubscribeHandler_TestCase(unittest.TestCase):
def setUp(self):
self.handler = DefaultSubscribeHandler(None)
def test_handle(self):
presence = Presence(from_jid="user1@test.com",
to_jid="user1%test.com@jcl.test.com",
stanza_type="subscribe")
result = self.handler.handle(presence, None, [])
self.assertEquals(len(result), 2)
self.assertEquals(result[0].get_to(), "user1@test.com")
self.assertEquals(result[0].get_from(), "user1%test.com@jcl.test.com")
self.assertEquals(result[0].get_type(), "subscribe")
self.assertEquals(result[1].get_to(), "user1@test.com")
self.assertEquals(result[1].get_from(), "user1%test.com@jcl.test.com")
self.assertEquals(result[1].get_type(), "subscribed")
class DefaultUnsubscribeHandler_TestCase(unittest.TestCase):
def setUp(self):
self.handler = DefaultUnsubscribeHandler(None)
def test_handle(self):
presence = Presence(from_jid="user1@test.com",
to_jid="user1%test.com@jcl.test.com",
stanza_type="unsubscribe")
result = self.handler.handle(presence, None, [])
self.assertEquals(len(result), 2)
self.assertEquals(result[0].get_to(), "user1@test.com")
self.assertEquals(result[0].get_from(), "user1%test.com@jcl.test.com")
self.assertEquals(result[0].get_type(), "unsubscribe")
self.assertEquals(result[1].get_to(), "user1@test.com")
self.assertEquals(result[1].get_from(), "user1%test.com@jcl.test.com")
self.assertEquals(result[1].get_type(), "unsubscribed")
class DefaultPresenceHandler_TestCase(unittest.TestCase):
def setUp(self):
self.handler = DefaultPresenceHandler(None)
def test_handle_away(self):
presence = Presence(from_jid="user1@test.com",
to_jid="user1%test.com@jcl.test.com",
stanza_type="available",
show="away")
result = self.handler.handle(presence, None, [])
self.assertEquals(len(result), 1)
self.assertEquals(result[0].get_to(), "user1@test.com")
self.assertEquals(result[0].get_from(), "user1%test.com@jcl.test.com")
self.assertEquals(result[0].get_type(), None)
self.assertEquals(result[0].get_show(), "away")
def test_handle_offline(self):
presence = Presence(from_jid="user1@test.com",
to_jid="user1%test.com@jcl.test.com",
stanza_type="unavailable")
result = self.handler.handle(presence, None, [])
self.assertEquals(len(result), 1)
self.assertEquals(result[0].get_to(), "user1@test.com")
self.assertEquals(result[0].get_from(), "user1%test.com@jcl.test.com")
self.assertEquals(result[0].get_type(), "unavailable")
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(DefaultSubscribeHandler_TestCase, 'test'))
suite.addTest(unittest.makeSuite(DefaultUnsubscribeHandler_TestCase, 'test'))
suite.addTest(unittest.makeSuite(DefaultPresenceHandler_TestCase, 'test'))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')

View File

@@ -1,3 +1,32 @@
"""Contains data model classes""" """Contains data model classes"""
__revision__ = "" __revision__ = ""
from sqlobject.dbconnection import ConnectionHub
from sqlobject.dbconnection import connectionForURI
import jcl.model
db_connected = False
db_connection_str = ""
# create a hub to attach a per thread connection
hub = ConnectionHub()
def db_connect():
"""
Create a new connection to the DataBase (SQLObject use connection
pool) associated to the current thread.
"""
if not jcl.model.db_connected:
jcl.model.hub.threadConnection = \
connectionForURI(db_connection_str)
# account.hub.threadConnection.debug = True
jcl.model.db_connected = True
def db_disconnect():
"""
Delete connection associated to the current thread.
"""
if jcl.model.db_connected:
del jcl.model.hub.threadConnection
jcl.model.db_connected = False

View File

@@ -28,16 +28,16 @@ __revision__ = "$Id: account.py,v 1.3 2005/09/18 20:24:07 dax Exp $"
from sqlobject.inheritance import InheritableSQLObject from sqlobject.inheritance import InheritableSQLObject
from sqlobject.col import StringCol, EnumCol, IntCol, BoolCol, ForeignKey from sqlobject.col import StringCol, EnumCol, IntCol, BoolCol, ForeignKey
from sqlobject.dbconnection import ConnectionHub
from sqlobject.joins import MultipleJoin from sqlobject.joins import MultipleJoin
from sqlobject.sqlbuilder import AND
from jcl.lang import Lang from jcl.lang import Lang
from jcl.error import FieldError from jcl.error import FieldError
import jcl.model as model
OFFLINE = "offline" OFFLINE = "offline"
ONLINE = "online" ONLINE = "online"
def default_post_func(field_value, default_func, bare_from_jid): def default_post_func(field_value, default_func, bare_from_jid):
"""Default post process function: do nothing""" """Default post process function: do nothing"""
if field_value is None or str(field_value) == "": if field_value is None or str(field_value) == "":
@@ -57,13 +57,32 @@ def mandatory_field(field_value):
raise FieldError # TODO : add translated message raise FieldError # TODO : add translated message
return field_value return field_value
# create a hub to attach a per thread connection def get_account(name, bare_user_jid):
hub = ConnectionHub() result = None
model.db_connect()
accounts = Account.select(\
AND(Account.q.name == name,
Account.q.user_jid == unicode(bare_user_jid)))
if accounts.count() > 0:
result = accounts[0]
model.db_disconnect()
return result
def get_accounts(bare_user_jid):
model.db_connect()
accounts = Account.select(\
Account.q.user_jid == unicode(bare_user_jid))
if accounts.count() == 0:
model.db_disconnect()
return
for _account in accounts:
yield _account
model.db_disconnect()
class Account(InheritableSQLObject): class Account(InheritableSQLObject):
"""Base Account class""" """Base Account class"""
_cacheValue = False _cacheValue = False
_connection = hub _connection = model.hub
user_jid = StringCol() user_jid = StringCol()
name = StringCol() name = StringCol()
jid = StringCol() jid = StringCol()
@@ -268,8 +287,17 @@ class PresenceAccount(Account):
action = property(get_action) action = property(get_action)
def get_legacy_jids(bare_to_jid):
model.db_connect()
legacy_jids = LegacyJID.select(\
AND(LegacyJID.q.accountID == Account.q.id,
Account.q.user_jid == bare_to_jid))
for legacy_jid in legacy_jids:
yield legacy_jid
model.db_disconnect()
class LegacyJID(InheritableSQLObject): class LegacyJID(InheritableSQLObject):
_connection = hub _connection = model.hub
legacy_address = StringCol() legacy_address = StringCol()
jid = StringCol() jid = StringCol()

View File

@@ -28,6 +28,7 @@ from sqlobject import *
from sqlobject.dbconnection import TheURIOpener from sqlobject.dbconnection import TheURIOpener
from jcl.error import FieldError from jcl.error import FieldError
import jcl.model as model
from jcl.model import account from jcl.model import account
from jcl.model.account import Account, PresenceAccount from jcl.model.account import Account, PresenceAccount
@@ -165,13 +166,15 @@ class AccountModule_TestCase(unittest.TestCase):
class InheritableAccount_TestCase(unittest.TestCase): class InheritableAccount_TestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.db_url = DB_URL self.db_url = DB_URL
model.db_connection_str = 'sqlite://' + self.db_url
def test_get_register_fields(self): def test_get_register_fields(self):
"""Check if post functions and default functions execute correctly. """
Check if post functions and default functions execute correctly.
To be validated this test only need to be executed without any To be validated this test only need to be executed without any
exception. exception.
""" """
account.hub.threadConnection = connectionForURI('sqlite://' + self.db_url) model.db_connect()
for (field_name, for (field_name,
field_type, field_type,
field_options, field_options,
@@ -184,41 +187,42 @@ class InheritableAccount_TestCase(unittest.TestCase):
except FieldError, error: except FieldError, error:
# this type of error is OK # this type of error is OK
pass pass
del account.hub.threadConnection model.db_disconnect()
class Account_TestCase(InheritableAccount_TestCase): class Account_TestCase(InheritableAccount_TestCase):
def setUp(self): def setUp(self):
if os.path.exists(DB_PATH): if os.path.exists(DB_PATH):
os.unlink(DB_PATH) os.unlink(DB_PATH)
self.db_url = DB_URL self.db_url = DB_URL
account.hub.threadConnection = connectionForURI('sqlite://' + self.db_url) model.db_connection_str = 'sqlite://' + self.db_url
model.db_connect()
Account.createTable(ifNotExists = True) Account.createTable(ifNotExists = True)
ExampleAccount.createTable(ifNotExists = True) ExampleAccount.createTable(ifNotExists = True)
del account.hub.threadConnection model.db_disconnect()
self.account_class = Account self.account_class = Account
def tearDown(self): def tearDown(self):
account.hub.threadConnection = connectionForURI('sqlite://' + self.db_url) model.db_connect()
ExampleAccount.dropTable(ifExists = True) ExampleAccount.dropTable(ifExists = True)
Account.dropTable(ifExists = True) Account.dropTable(ifExists = True)
del TheURIOpener.cachedURIs['sqlite://' + self.db_url] del TheURIOpener.cachedURIs['sqlite://' + self.db_url]
account.hub.threadConnection.close() model.hub.threadConnection.close()
del account.hub.threadConnection model.db_disconnect()
if os.path.exists(DB_PATH): if os.path.exists(DB_PATH):
os.unlink(DB_PATH) os.unlink(DB_PATH)
def test_set_status(self): def test_set_status(self):
account.hub.threadConnection = connectionForURI('sqlite://' + self.db_url) model.db_connect()
account11 = Account(user_jid="test1@test.com", account11 = Account(user_jid="test1@test.com",
name="account11", name="account11",
jid="account11@jcl.test.com") jid="account11@jcl.test.com")
account11.status = account.OFFLINE account11.status = account.OFFLINE
self.assertEquals(account11.status, account.OFFLINE) self.assertEquals(account11.status, account.OFFLINE)
# TODO : test first_check attribute # TODO : test first_check attribute
del account.hub.threadConnection model.db_disconnect()
def test_set_status_live_password(self): def test_set_status_live_password(self):
account.hub.threadConnection = connectionForURI('sqlite://' + self.db_url) model.db_connect()
account11 = ExampleAccount(user_jid="test1@test.com", account11 = ExampleAccount(user_jid="test1@test.com",
name="account11", name="account11",
jid="account11@jcl.test.com", jid="account11@jcl.test.com",
@@ -232,32 +236,33 @@ class Account_TestCase(InheritableAccount_TestCase):
self.assertEquals(account11.status, account.OFFLINE) self.assertEquals(account11.status, account.OFFLINE)
self.assertEquals(account11.waiting_password_reply, False) self.assertEquals(account11.waiting_password_reply, False)
self.assertEquals(account11.password, None) self.assertEquals(account11.password, None)
del account.hub.threadConnection model.db_disconnect()
class PresenceAccount_TestCase(InheritableAccount_TestCase): class PresenceAccount_TestCase(InheritableAccount_TestCase):
def setUp(self): def setUp(self):
if os.path.exists(DB_PATH): if os.path.exists(DB_PATH):
os.unlink(DB_PATH) os.unlink(DB_PATH)
self.db_url = DB_URL self.db_url = DB_URL
account.hub.threadConnection = connectionForURI('sqlite://' + self.db_url) model.db_connection_str = 'sqlite://' + self.db_url
model.db_connect()
Account.createTable(ifNotExists = True) Account.createTable(ifNotExists = True)
PresenceAccount.createTable(ifNotExists = True) PresenceAccount.createTable(ifNotExists = True)
PresenceAccountExample.createTable(ifNotExists = True) PresenceAccountExample.createTable(ifNotExists = True)
self.account = PresenceAccountExample(\ self.account = PresenceAccountExample(\
user_jid = "test1@test.com", \ user_jid="test1@test.com",
name = "account11", \ name="account11",
jid = "account11@jcl.test.com") jid="account11@jcl.test.com")
del account.hub.threadConnection model.db_disconnect()
self.account_class = PresenceAccount self.account_class = PresenceAccount
def tearDown(self): def tearDown(self):
account.hub.threadConnection = connectionForURI('sqlite://' + self.db_url) model.db_connect()
PresenceAccountExample.dropTable(ifExists = True) PresenceAccountExample.dropTable(ifExists = True)
PresenceAccount.dropTable(ifExists = True) PresenceAccount.dropTable(ifExists = True)
Account.dropTable(ifExists = True) Account.dropTable(ifExists = True)
del TheURIOpener.cachedURIs['sqlite://' + self.db_url] del TheURIOpener.cachedURIs['sqlite://' + self.db_url]
account.hub.threadConnection.close() model.hub.threadConnection.close()
del account.hub.threadConnection model.db_disconnect()
if os.path.exists(DB_PATH): if os.path.exists(DB_PATH):
os.unlink(DB_PATH) os.unlink(DB_PATH)

View File

@@ -28,6 +28,7 @@ from getopt import gnu_getopt
from sqlobject import * from sqlobject import *
import jcl.model as model
from jcl.model import account from jcl.model import account
from jcl.model.account import Account, PresenceAccount from jcl.model.account import Account, PresenceAccount
@@ -48,35 +49,35 @@ class JCLRunner(object):
self.language = "en" self.language = "en"
self.db_url = "sqlite:///var/spool/jabber/jcl.db" self.db_url = "sqlite:///var/spool/jabber/jcl.db"
self.pid_file = "/var/run/jabber/jcl.pid" self.pid_file = "/var/run/jabber/jcl.pid"
self.options = [("c:", "config-file=", None, \ self.options = [("c:", "config-file=", None,
" FILE\t\t\t\tConfiguration file to use", \ " FILE\t\t\t\tConfiguration file to use",
lambda arg: self.set_attr("config_file", arg)), \ lambda arg: self.set_attr("config_file", arg)),
("S:", "server=", "jabber", \ ("S:", "server=", "jabber",
" SERVER_ADDRESS\t\t\tAddress of the Jabber server", \ " SERVER_ADDRESS\t\t\tAddress of the Jabber server",
lambda arg: self.set_attr("server", arg)), \ lambda arg: self.set_attr("server", arg)),
("P:", "port=", "jabber", \ ("P:", "port=", "jabber",
" PORT\t\t\t\t\tPort of the Jabber server to connect the component", \ " PORT\t\t\t\t\tPort of the Jabber server to connect the component",
lambda arg: self.set_attr("port", int(arg))), \ lambda arg: self.set_attr("port", int(arg))),
("s:", "secret=", "jabber", \ ("s:", "secret=", "jabber",
" SECRET\t\t\t\tComponent password to connect to the Jabber server", \ " SECRET\t\t\t\tComponent password to connect to the Jabber server",
lambda arg: self.set_attr("secret", arg)), \ lambda arg: self.set_attr("secret", arg)),
("j:", "service-jid=", "jabber", \ ("j:", "service-jid=", "jabber",
" JID\t\t\t\tJID of the component", \ " JID\t\t\t\tJID of the component",
lambda arg: self.set_attr("service_jid", arg)), \ lambda arg: self.set_attr("service_jid", arg)),
("l:", "language=", "jabber", \ ("l:", "language=", "jabber",
" LANG\t\t\t\tDefault Language of the component", \ " LANG\t\t\t\tDefault Language of the component",
lambda arg: self.set_attr("language", arg)), \ lambda arg: self.set_attr("language", arg)),
("u:", "db-url=", "db", \ ("u:", "db-url=", "db",
" URL\t\t\t\tDatabase URL", \ " URL\t\t\t\tDatabase URL",
lambda arg: self.set_attr("db_url", arg)), \ lambda arg: self.set_attr("db_url", arg)),
("p:", "pid-file=", "component", \ ("p:", "pid-file=", "component",
" FILE\t\t\t\tPath of the PID file", \ " FILE\t\t\t\tPath of the PID file",
lambda arg: self.set_attr("pid_file", arg)), \ lambda arg: self.set_attr("pid_file", arg)),
("d", "debug", None, \ ("d", "debug", None,
"\t\t\t\t\tEnable debug traces", \ "\t\t\t\t\tEnable debug traces",
lambda arg: self.set_attr("debug", True)), \ lambda arg: self.set_attr("debug", True)),
("h", "help", None, \ ("h", "help", None,
"\t\t\t\t\tThis help", \ "\t\t\t\t\tThis help",
lambda arg: self.print_help())] lambda arg: self.print_help())]
self.logger = logging.getLogger() self.logger = logging.getLogger()
self.logger.addHandler(logging.StreamHandler()) self.logger.addHandler(logging.StreamHandler())
@@ -97,7 +98,7 @@ class JCLRunner(object):
def __apply_commandline_args(self, commandline_args, cleanopts): def __apply_commandline_args(self, commandline_args, cleanopts):
for arg in commandline_args: for arg in commandline_args:
value = commandline_args[arg] value = commandline_args[arg]
self.logger.debug("Applying argument " + arg + " = " + \ self.logger.debug("Applying argument " + arg + " = " +
value) value)
cleanopts[arg][1](value) cleanopts[arg][1](value)
@@ -118,9 +119,9 @@ class JCLRunner(object):
if section is not None: if section is not None:
attr = opt.replace("-", "_") attr = opt.replace("-", "_")
config_property = self.config.get(section, attr) config_property = self.config.get(section, attr)
self.logger.debug("Setting " + attr + " = " + \ self.logger.debug("Setting " + attr + " = " +
config_property + \ config_property +
" from configuration file " + \ " from configuration file " +
self.config_file) self.config_file)
set_func(config_property) set_func(config_property)
@@ -175,8 +176,8 @@ class JCLRunner(object):
def setup_db(self): def setup_db(self):
Account.createTable(ifNotExists = True) Account.createTable(ifNotExists=True)
PresenceAccount.createTable(ifNotExists = True) PresenceAccount.createTable(ifNotExists=True)
def setup_pidfile(self): def setup_pidfile(self):
pidfile = open(self.pid_file, "w") pidfile = open(self.pid_file, "w")
@@ -186,10 +187,11 @@ class JCLRunner(object):
def _run(self, run_func): def _run(self, run_func):
try: try:
self.setup_pidfile() self.setup_pidfile()
account.hub.threadConnection = connectionForURI(self.db_url) model.db_connection_str = self.db_url
model.db_connect()
self.setup_db() self.setup_db()
del account.hub.threadConnection model.db_disconnect()
self.logger.debug(self.component_name + " v" + \ self.logger.debug(self.component_name + " v" +
self.component_version + " is starting ...") self.component_version + " is starting ...")
run_func() run_func()
self.logger.debug(self.component_name + " is exiting") self.logger.debug(self.component_name + " is exiting")
@@ -199,12 +201,11 @@ class JCLRunner(object):
def run(self): def run(self):
def run_func(): def run_func():
component = JCLComponent(jid = self.service_jid, \ component = JCLComponent(jid=self.service_jid,
secret = self.secret, \ secret=self.secret,
server = self.server, \ server=self.server,
port = self.port, \ port=self.port,
db_connection_str = self.db_url, \ lang=Lang(self.language))
lang = Lang(self.language))
component.run() component.run()
self._run(run_func) self._run(run_func)

View File

@@ -29,6 +29,7 @@ from sqlobject import *
import jcl import jcl
from jcl.runner import JCLRunner from jcl.runner import JCLRunner
import jcl.model as model
from jcl.model import account from jcl.model import account
from jcl.model.account import Account, PresenceAccount from jcl.model.account import Account, PresenceAccount
@@ -125,11 +126,11 @@ class JCLRunner_TestCase(unittest.TestCase):
def do_nothing(): def do_nothing():
pass pass
self.runner._run(do_nothing) self.runner._run(do_nothing)
account.hub.threadConnection = connectionForURI(self.runner.db_url) model.db_connect()
# dropTable should succeed because tables should exist # dropTable should succeed because tables should exist
Account.dropTable() Account.dropTable()
PresenceAccount.dropTable() PresenceAccount.dropTable()
del account.hub.threadConnection model.db_disconnect()
os.unlink(DB_PATH) os.unlink(DB_PATH)
self.assertFalse(os.access("/tmp/jcl.pid", os.F_OK)) self.assertFalse(os.access("/tmp/jcl.pid", os.F_OK))