From 78ab52627f315bc3a2fb054e3e350623a8383b95 Mon Sep 17 00:00:00 2001 From: David Rousselie Date: Wed, 23 May 2007 22:49:35 +0200 Subject: [PATCH] Add plugable handlers and define default handlers * pluggable handlers for presence_available, presence_unavailable, presence_subscribe, presence_subscribed, message * define default handlers behavior DefaultSubscribeHandler, DefaultUnsubscribeHandler, DefaultPresenceHandler darcs-hash:20070523204935-86b55-c45e0631b5694d35d8aac606c1d1f5772bc5f15a.gz --- src/jcl/jabber/component.py | 115 ++++++++++++---- src/jcl/jabber/tests/component.py | 218 +++++++++++++++++++++++++++++- src/jcl/jabber/tests/feeder.py | 4 +- src/jcl/model/tests/account.py | 12 +- src/jcl/runner.py | 1 + 5 files changed, 307 insertions(+), 43 deletions(-) diff --git a/src/jcl/jabber/component.py b/src/jcl/jabber/component.py index d584eda..e002104 100644 --- a/src/jcl/jabber/component.py +++ b/src/jcl/jabber/component.py @@ -94,6 +94,10 @@ class JCLComponent(Component, object): self.queue = Queue(100) self.account_manager = AccountManager(self) self.msg_handlers = [] + self.subscribe_handlers = [] + self.unsubscribe_handlers = [] + self.available_handlers = [] + self.unavailable_handlers = [] self.__logger = logging.getLogger("jcl.jabber.JCLComponent") self.lang = lang @@ -250,7 +254,19 @@ class JCLComponent(Component, object): if send_result: self.send_stanzas(result) return result - + + def apply_registered_behavior(self, handlers, stanza, apply_all = False): + """Execute handler if their filter method does not return None""" + result = [] + self.db_connect() + for handler in handlers: + accounts = handler.filter(stanza) + if accounts is not None: + result += handler.handle(stanza, self.lang, accounts) + self.db_disconnect() + self.send_stanzas(result) + return result + def disco_get_info(self, node, info_query): """Discovery get info handler """ @@ -362,9 +378,10 @@ class JCLComponent(Component, object): """Handle presence availability if presence sent to the component ('if not name'), presence is sent to all accounts for current user. Otherwise, send presence from current account. - """ - return self.apply_behavior(stanza, \ + result = self.apply_registered_behavior(self.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, \ @@ -377,13 +394,16 @@ class JCLComponent(Component, object): lang_class, \ stanza.get_show()), \ send_result = True) + return result def handle_presence_unavailable(self, stanza): """Handle presence unavailability """ self.__logger.debug("PRESENCE_UNAVAILABLE") - return self.apply_behavior(stanza, \ + result = self.apply_registered_behavior(self.unavailable_handlers, stanza) + if result == []: + result = self.apply_behavior(stanza, \ lambda name, from_jid, account_type, lang_class: \ self.account_manager.account_handle_presence_unavailable(name, \ from_jid), \ @@ -392,12 +412,15 @@ class JCLComponent(Component, object): lambda name, from_jid, account_type, lang_class: \ self.account_manager.root_handle_presence_unavailable(from_jid), \ send_result = True) + return result def handle_presence_subscribe(self, stanza): """Handle subscribe presence from user """ self.__logger.debug("PRESENCE_SUBSCRIBE") - return self.apply_behavior(stanza, \ + result = self.apply_registered_behavior(self.subscribe_handlers, stanza) + if result == []: + result = self.apply_behavior(stanza, \ lambda name, from_jid, account_type, lang_class: \ self.account_manager.account_handle_presence_subscribe(name, \ from_jid, \ @@ -408,6 +431,7 @@ class JCLComponent(Component, object): self.account_manager.root_handle_presence_subscribe(from_jid, \ stanza), \ send_result = True) + return result def handle_presence_subscribed(self, stanza): """Handle subscribed presence from user @@ -419,7 +443,9 @@ class JCLComponent(Component, object): """Handle unsubscribe presence from user """ self.__logger.debug("PRESENCE_UNSUBSCRIBE") - return self.apply_behavior(stanza, \ + result = self.apply_registered_behavior(self.unsubscribe_handlers, stanza) + if result == []: + result = self.apply_behavior(stanza, \ lambda name, from_jid, account_type, lang_class: \ self.account_manager.account_handle_presence_unsubscribe(name, \ from_jid), \ @@ -428,6 +454,7 @@ class JCLComponent(Component, object): lambda name, from_jid, account_type, lang_class: \ [], \ send_result = True) + return result def handle_presence_unsubscribed(self, stanza): """Handle unsubscribed presence from user @@ -444,14 +471,7 @@ class JCLComponent(Component, object): Handle password response message """ self.__logger.debug("MESSAGE: " + message.get_body()) - result = [] - self.db_connect() - for msg_handler in self.msg_handlers: - accounts = msg_handler.filter(message) - if accounts is not None: - result += msg_handler.handle(message, self.lang, accounts) - self.db_disconnect() - self.send_stanzas(result) + self.apply_registered_behavior(self.msg_handlers, message) return 1 ########################################################################### @@ -1093,32 +1113,71 @@ class AccountManager(object): % (exception))) return result -class MessageHandler(object): - """Message handling class""" +class Handler(object): + """handling class""" - def filter(self, message): + def filter(self, stanza): """Filter account to be processed by the handler return all accounts. DB connection might already be opened.""" accounts = Account.select() return accounts - def handle(self, message, lang, accounts): + def handle(self, stanza, lang, accounts): """Apply actions to do on given accounts Do nothing by default""" return [] -class PasswordMessageHandler(MessageHandler): +class DefaultPresenceHandler(Handler): + """Handle presence""" + + def handle(self, presence, lang, accounts): + """Return same presence as receive one""" + to_jid = presence.get_to() + from_jid = presence.get_from() + presence.set_to(from_jid) + presence.set_from(to_jid) + return [presence] + +class DefaultSubscribeHandler(Handler): + """Return default response to subscribe queries""" + + def handle(self, stanza, lang, accounts): + """Create subscribe response""" + result = [] + result.append(Presence(from_jid = stanza.get_to(), \ + to_jid = stanza.get_from(), \ + stanza_type = "subscribe")) + result.append(Presence(from_jid = stanza.get_to(), \ + to_jid = stanza.get_from(), \ + stanza_type = "subscribed")) + return result + +class DefaultUnsubscribeHandler(Handler): + """Return default response to subscribe queries""" + + def handle(self, stanza, lang, accounts): + """Create subscribe response""" + result = [] + result.append(Presence(from_jid = stanza.get_to(), \ + to_jid = stanza.get_from(), \ + stanza_type = "unsubscribe")) + result.append(Presence(from_jid = stanza.get_to(), \ + to_jid = stanza.get_from(), \ + stanza_type = "unsubscribed")) + return result + +class PasswordMessageHandler(Handler): """Handle password message""" def __init__(self): """á¸Ĥandler constructor""" self.password_regexp = re.compile("\[PASSWORD\]") - def filter(self, message): + def filter(self, stanza): """Return the uniq account associated with a name and user JID. DB connection might already be opened.""" - name = message.get_to().node - bare_from_jid = unicode(message.get_from().bare()) + name = stanza.get_to().node + bare_from_jid = unicode(stanza.get_from().bare()) accounts = Account.select(\ AND(Account.q.name == name, \ Account.q.user_jid == bare_from_jid)) @@ -1128,20 +1187,20 @@ class PasswordMessageHandler(MessageHandler): if hasattr(_account, 'password') \ and hasattr(_account, 'waiting_password_reply') \ and (getattr(_account, 'waiting_password_reply') == True) \ - and self.password_regexp.search(message.get_subject()) \ + and self.password_regexp.search(stanza.get_subject()) \ is not None: return accounts else: return None - def handle(self, message, lang, accounts): - """Receive password for given account""" + def handle(self, stanza, lang, accounts): + """Receive password in stanza (must be a Message) for given account""" _account = accounts[0] - lang_class = lang.get_lang_class_from_node(message.get_node()) - _account.password = message.get_body() + lang_class = lang.get_lang_class_from_node(stanza.get_node()) + _account.password = stanza.get_body() _account.waiting_password_reply = False return [Message(from_jid = _account.jid, \ - to_jid = message.get_from(), \ + to_jid = stanza.get_from(), \ stanza_type = "normal", \ subject = lang_class.password_saved_for_session, \ body = lang_class.password_saved_for_session)] diff --git a/src/jcl/jabber/tests/component.py b/src/jcl/jabber/tests/component.py index 8fa0bb8..79d242c 100644 --- a/src/jcl/jabber/tests/component.py +++ b/src/jcl/jabber/tests/component.py @@ -28,6 +28,7 @@ import threading import time import sys import os +import re from sqlobject import * from sqlobject.dbconnection import TheURIOpener @@ -40,7 +41,7 @@ from pyxmpp.presence import Presence from pyxmpp.message import Message from pyxmpp.jabber.dataforms import Form, Field, Option -from jcl.jabber.component import JCLComponent, MessageHandler, PasswordMessageHandler +from jcl.jabber.component import JCLComponent, Handler, PasswordMessageHandler, DefaultSubscribeHandler, DefaultUnsubscribeHandler, DefaultPresenceHandler from jcl.model import account from jcl.model.account import Account from jcl.lang import Lang @@ -48,9 +49,9 @@ from jcl.lang import Lang from jcl.model.tests.account import ExampleAccount, Example2Account if sys.platform == "win32": - DB_PATH = "/c|/temp/test.db" + DB_PATH = "/c|/temp/jcl_test.db" else: - DB_PATH = "/tmp/test.db" + DB_PATH = "/tmp/jcl_test.db" DB_URL = DB_PATH# + "?debug=1&debugThreading=1" class MockStream(object): @@ -125,6 +126,22 @@ class LangExample(Lang): class en(Lang.en): type_example_name = "Type Example" +class TestSubscribeHandler(DefaultSubscribeHandler): + def filter(self, message): + if re.compile(".*%.*").match(message.get_to().node): + # return no account because self.handle does not need an account + return [] + else: + return None + +class TestUnsubscribeHandler(DefaultUnsubscribeHandler): + def filter(self, message): + if re.compile(".*%.*").match(message.get_to().node): + # return no account because self.handle does not need an account + return [] + else: + return None + class JCLComponent_TestCase(unittest.TestCase): ########################################################################### # Utility methods @@ -1398,6 +1415,31 @@ class JCLComponent_TestCase(unittest.TestCase): self.assertEqual(presence_sent[0].get_from(), "account11@jcl.test.com") self.assertTrue(isinstance(presence_sent[0], Presence)) + def test_handle_presence_available_to_registered_handlers(self): + self.comp.stream = MockStream() + self.comp.stream_class = MockStream + self.comp.available_handlers += [DefaultPresenceHandler()] + account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) + 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") + account2 = Account(user_jid = "user2@test.com", \ + name = "account2", \ + jid = "account2@jcl.test.com") + del account.hub.threadConnection + self.comp.handle_presence_available(Presence(\ + stanza_type = "available", \ + from_jid = "user1@test.com",\ + to_jid = "user1%test.com@jcl.test.com")) + presence_sent = self.comp.stream.sent + self.assertEqual(len(presence_sent), 1) + self.assertEqual(presence_sent[0].get_to(), "user1@test.com") + self.assertEqual(presence_sent[0].get_from(), "user1%test.com@jcl.test.com") + self.assertTrue(isinstance(presence_sent[0], Presence)) + def test_handle_presence_available_to_account_unknown_user(self): self.comp.stream = MockStream() self.comp.stream_class = MockStream @@ -1606,6 +1648,33 @@ class JCLComponent_TestCase(unittest.TestCase): presence_sent[0].xpath_eval("@type")[0].get_content(), \ "unavailable") + def test_handle_presence_unavailable_to_registered_handlers(self): + self.comp.stream = MockStream() + self.comp.stream_class = MockStream + self.comp.unavailable_handlers += [DefaultPresenceHandler()] + account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) + 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") + account2 = Account(user_jid = "user2@test.com", \ + name = "account2", \ + jid = "account2@jcl.test.com") + del account.hub.threadConnection + self.comp.handle_presence_unavailable(Presence(\ + stanza_type = "unavailable", \ + from_jid = "user1@test.com",\ + to_jid = "user1%test.com@jcl.test.com")) + presence_sent = self.comp.stream.sent + self.assertEqual(len(presence_sent), 1) + self.assertEqual(presence_sent[0].get_to(), "user1@test.com") + self.assertEqual(presence_sent[0].get_from(), "user1%test.com@jcl.test.com") + self.assertEqual(\ + presence_sent[0].xpath_eval("@type")[0].get_content(), \ + "unavailable") + def test_handle_presence_unavailable_to_account_unknown_user(self): self.comp.stream = MockStream() self.comp.stream_class = MockStream @@ -1763,6 +1832,32 @@ class JCLComponent_TestCase(unittest.TestCase): presence_sent = self.comp.stream.sent self.assertEqual(len(presence_sent), 0) + def test_handle_presence_subscribe_to_registered_handlers(self): + self.comp.stream = MockStream() + self.comp.stream_class = MockStream + self.comp.subscribe_handlers += [DefaultSubscribeHandler()] + account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) + 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") + account2 = Account(user_jid = "user2@test.com", \ + name = "account2", \ + jid = "account2@jcl.test.com") + del account.hub.threadConnection + result = self.comp.handle_presence_subscribe(Presence(\ + stanza_type = "subscribe", \ + from_jid = "user1@test.com",\ + to_jid = "user1%test.com@jcl.test.com")) + sent = self.comp.stream.sent + self.assertEqual(len(sent), 2) + self.assertTrue(type(sent[0]), Presence) + self.assertEquals(sent[0].get_type(), "subscribe") + self.assertTrue(type(sent[1]), Presence) + self.assertEquals(sent[1].get_type(), "subscribed") + def test_handle_presence_subscribed(self): self.comp.stream = MockStream() self.comp.stream_class = MockStream @@ -1811,6 +1906,38 @@ class JCLComponent_TestCase(unittest.TestCase): 2) del account.hub.threadConnection + def test_handle_presence_unsubscribe_to_registered_handlers(self): + self.comp.stream = MockStream() + self.comp.stream_class = MockStream + self.comp.unsubscribe_handlers += [DefaultUnsubscribeHandler()] + account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) + 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") + account2 = Account(user_jid = "user2@test.com", \ + name = "account2", \ + jid = "account2@jcl.test.com") + del account.hub.threadConnection + self.comp.handle_presence_unsubscribe(Presence(\ + stanza_type = "unsubscribe", \ + from_jid = "user1@test.com",\ + to_jid = "user1%test.com@jcl.test.com")) + presence_sent = self.comp.stream.sent + self.assertEqual(len(presence_sent), 2) + presence = presence_sent[0] + self.assertEqual(presence.get_from(), "user1%test.com@jcl.test.com") + self.assertEqual(presence.get_to(), "user1@test.com") + self.assertEqual(presence.xpath_eval("@type")[0].get_content(), \ + "unsubscribe") + presence = presence_sent[1] + self.assertEqual(presence.get_from(), "user1%test.com@jcl.test.com") + self.assertEqual(presence.get_to(), "user1@test.com") + self.assertEqual(presence.xpath_eval("@type")[0].get_content(), \ + "unsubscribed") + def test_handle_presence_unsubscribe_to_account_unknown_user(self): self.comp.stream = MockStream() self.comp.stream_class = MockStream @@ -1836,7 +1963,6 @@ class JCLComponent_TestCase(unittest.TestCase): 3) del account.hub.threadConnection - def test_handle_presence_unsubscribe_to_unknown_account(self): self.comp.stream = MockStream() self.comp.stream_class = MockStream @@ -1877,6 +2003,21 @@ class JCLComponent_TestCase(unittest.TestCase): presence_sent[0].xpath_eval("@type")[0].get_content(), \ "unavailable") + def test_handle_presence_unsubscribed_to_registered_handler(self): + self.comp.stream = MockStream() + self.comp.stream_class = MockStream + self.comp.handle_presence_unsubscribed(Presence(\ + stanza_type = "unsubscribed", \ + from_jid = "user1@test.com",\ + to_jid = "user1%test.com@jcl.test.com")) + presence_sent = self.comp.stream.sent + self.assertEqual(len(presence_sent), 1) + self.assertEqual(presence_sent[0].get_to(), "user1@test.com") + self.assertEqual(presence_sent[0].get_from(), "user1%test.com@jcl.test.com") + self.assertEqual(\ + presence_sent[0].xpath_eval("@type")[0].get_content(), \ + "unavailable") + def test_handle_message_password(self): self.comp.stream = MockStream() self.comp.stream_class = MockStream @@ -1983,9 +2124,9 @@ class JCLComponent_TestCase(unittest.TestCase): self.comp.send_stanzas(None) self.assertEquals(len(self.comp.stream.sent), 0) -class MessageHandler_TestCase(unittest.TestCase): +class Handler_TestCase(unittest.TestCase): def setUp(self): - self.handler = MessageHandler() + self.handler = Handler() if os.path.exists(DB_PATH): os.unlink(DB_PATH) account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) @@ -2016,6 +2157,66 @@ class MessageHandler_TestCase(unittest.TestCase): def test_handle(self): self.assertEquals(self.handler.handle(None, None, None), []) +class DefaultSubscribeHandler_TestCase(unittest.TestCase): + def setUp(self): + self.handler = DefaultSubscribeHandler() + + 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() + + 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() + + 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") + class PasswordMessageHandler_TestCase(unittest.TestCase): def setUp(self): self.handler = PasswordMessageHandler() @@ -2129,7 +2330,10 @@ class PasswordMessageHandler_TestCase(unittest.TestCase): def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(JCLComponent_TestCase, 'test')) - suite.addTest(unittest.makeSuite(MessageHandler_TestCase, 'test')) + suite.addTest(unittest.makeSuite(Handler_TestCase, 'test')) + suite.addTest(unittest.makeSuite(DefaultSubscribeHandler_TestCase, 'test')) + suite.addTest(unittest.makeSuite(DefaultUnsubscribeHandler_TestCase, 'test')) + suite.addTest(unittest.makeSuite(DefaultPresenceHandler_TestCase, 'test')) suite.addTest(unittest.makeSuite(PasswordMessageHandler_TestCase, 'test')) return suite diff --git a/src/jcl/jabber/tests/feeder.py b/src/jcl/jabber/tests/feeder.py index 026c99f..5b2214d 100644 --- a/src/jcl/jabber/tests/feeder.py +++ b/src/jcl/jabber/tests/feeder.py @@ -41,9 +41,9 @@ from jcl.model.tests.account import ExampleAccount, Example2Account from jcl.jabber.tests.component import JCLComponent_TestCase, MockStream if sys.platform == "win32": - DB_PATH = "/c|/temp/test.db" + DB_PATH = "/c|/temp/jcl_test.db" else: - DB_PATH = "/tmp/test.db" + DB_PATH = "/tmp/jcl_test.db" DB_URL = DB_PATH #+ "?debug=1&debugThreading=1" class FeederComponent_TestCase(JCLComponent_TestCase): diff --git a/src/jcl/model/tests/account.py b/src/jcl/model/tests/account.py index 1c87fa6..59c58f0 100644 --- a/src/jcl/model/tests/account.py +++ b/src/jcl/model/tests/account.py @@ -31,6 +31,12 @@ from jcl.jabber.error import FieldError from jcl.model import account from jcl.model.account import Account, PresenceAccount +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 ExampleAccount(Account): login = StringCol(default = "") password = StringCol(default = None) @@ -110,12 +116,6 @@ class PresenceAccountExample(PresenceAccount): lambda : 43)] get_register_fields = classmethod(_get_register_fields) -if sys.platform == "win32": - DB_PATH = "/c|/temp/test.db" -else: - DB_PATH = "/tmp/test.db" -DB_URL = DB_PATH# + "?debug=1&debugThreading=1" - class AccountModule_TestCase(unittest.TestCase): def test_default_post_func(self): result = account.default_post_func("test", None) diff --git a/src/jcl/runner.py b/src/jcl/runner.py index 979f7bc..e53e1ac 100644 --- a/src/jcl/runner.py +++ b/src/jcl/runner.py @@ -143,6 +143,7 @@ class JCLRunner(object): commandline_args = self.__configure_commandline_args(shortopts, longopts, cleanopts) if commandline_args.has_key("debug") or commandline_args.has_key("d"): self.debug = True + self.logger.debug("Debug activated") self.__apply_configfile(commandline_args, cleanopts) self.__apply_commandline_args(commandline_args, cleanopts)