From ca0d636b3394b3ad076321ab690c7979cfdac5c5 Mon Sep 17 00:00:00 2001 From: David Rousselie Date: Thu, 5 Apr 2007 18:18:31 +0200 Subject: [PATCH] Finish AccountManager refactoring darcs-hash:20070405161831-86b55-592fee80ee5682e6fa13ac7a96432e362bec186a.gz --- run_tests.py | 2 +- src/jcl/jabber/component.py | 375 ++++++++++++++++------------- tests/jcl/jabber/test_component.py | 68 ++++-- 3 files changed, 244 insertions(+), 201 deletions(-) diff --git a/run_tests.py b/run_tests.py index 587ba10..e368590 100644 --- a/run_tests.py +++ b/run_tests.py @@ -58,7 +58,7 @@ if __name__ == '__main__': jcl_suite = unittest.TestSuite() # jcl_suite.addTest(FeederComponent_TestCase('test_handle_tick')) -# jcl_suite.addTest(JCLComponent_TestCase('test_handle_presence_available_to_component')) +# jcl_suite.addTest(JCLComponent_TestCase('test_run_go_offline')) # jcl_suite.addTest(PresenceAccount_TestCase('test_possibles_actions')) # jcl_suite = unittest.TestSuite((component_suite)) # jcl_suite = unittest.TestSuite((presence_account_suite)) diff --git a/src/jcl/jabber/component.py b/src/jcl/jabber/component.py index 83d4447..5a25e8d 100644 --- a/src/jcl/jabber/component.py +++ b/src/jcl/jabber/component.py @@ -130,24 +130,8 @@ class JCLComponent(Component, object): self.running = False if self.stream and not self.stream.eof \ and self.stream.socket is not None: - current_user_jid = None - self.db_connect() - # Explicit reference to account table (clauseTables) to use - # "user_jid" column with Account subclasses - for _account in \ - self.account_classes[0].select(clauseTables = ["account"], \ - orderBy = "user_jid"): - if current_user_jid != _account.user_jid: - current_user_jid = _account.user_jid - self.stream.send(Presence(\ - from_jid = unicode(self.jid), \ - to_jid = _account.user_jid, \ - stanza_type = "unavailable")) - self.stream.send(Presence(\ - from_jid = self.get_jid(_account), \ - to_jid = _account.user_jid, \ - stanza_type = "unavailable")) - self.db_disconnect() + presences = self.account_manager.send_presence_all("unavailable") + self.send_stanzas(presences) self.wait_event.set() timer_thread.join(JCLComponent.timeout) self.disconnect() @@ -406,64 +390,31 @@ class JCLComponent(Component, object): """Handle presence unavailability """ self.__logger.debug("PRESENCE_UNAVAILABLE") - from_jid = stanza.get_from() - base_from_jid = unicode(from_jid.bare()) - name = stanza.get_to().node - self.db_connect() - if not name: - accounts = self.account_classes[0].select(\ - self.account_classes[0].q.user_jid == base_from_jid) - for _account in accounts: - _account.status = jcl.model.account.OFFLINE - presence = Presence(from_jid = _account.jid, \ - to_jid = from_jid, \ - stanza_type = "unavailable") - self.stream.send(presence) - if accounts.count() > 0: - presence = Presence(from_jid = stanza.get_to(), \ - to_jid = from_jid, \ - stanza_type = "unavailable") - self.stream.send(presence) - else: - accounts = self.account_classes[0].select(\ - AND(self.account_classes[0].q.name == name, \ - self.account_classes[0].q.user_jid == base_from_jid)) - if accounts.count() > 0: - presence = Presence(from_jid = stanza.get_to(), \ - to_jid = from_jid, \ - stanza_type = "unavailable") - self.stream.send(presence) - self.db_disconnect() - return 1 + return 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) def handle_presence_subscribe(self, stanza): """Handle subscribe presence from user """ self.__logger.debug("PRESENCE_SUBSCRIBE") - from_jid = stanza.get_from() - base_from_jid = unicode(from_jid.bare()) - name = stanza.get_to().node - accounts = None - self.db_connect() - if not name: - self.__logger.debug("subscribe request on main jid") - accounts = self.account_classes[0].select(\ - self.account_classes[0].q.user_jid == base_from_jid) - else: - self.__logger.debug("subscribe request on '" + name + "' account") - accounts = self.account_classes[0].select(\ - AND(self.account_classes[0].q.name == name, \ - self.account_classes[0].q.user_jid == base_from_jid)) - if (accounts is not None \ - and accounts.count() > 0): - presence = stanza.make_accept_response() - self.stream.send(presence) - else: - self.__logger.debug("Account '" + str(name) + "' for user '" + \ - str(base_from_jid) + "' was not found. " + \ - "Refusing subscription") - self.db_disconnect() - return 1 + return 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) def handle_presence_subscribed(self, stanza): """Handle subscribed presence from user @@ -475,25 +426,15 @@ class JCLComponent(Component, object): """Handle unsubscribe presence from user """ self.__logger.debug("PRESENCE_UNSUBSCRIBE") - name = stanza.get_to().node - from_jid = stanza.get_from() - base_from_jid = unicode(from_jid.bare()) - self.db_connect() - accounts = self.account_classes[0].select(\ - AND(self.account_classes[0].q.name == name, \ - self.account_classes[0].q.user_jid == base_from_jid)) - for _account in accounts: - presence = Presence(from_jid = _account.jid, \ - to_jid = from_jid, \ - stanza_type = "unsubscribe") - self.stream.send(presence) - _account.destroySelf() - presence = Presence(from_jid = _account.jid, \ - to_jid = from_jid, \ - stanza_type = "unsubscribed") - self.stream.send(presence) - self.db_disconnect() - return 1 + return 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) def handle_presence_unsubscribed(self, stanza): """Handle unsubscribed presence from user @@ -537,79 +478,15 @@ class JCLComponent(Component, object): ########################################################################### # Utils ########################################################################### - # TODO : delete - def _get_account_class(self, account_class_name): - """Return account class definition from declared classes in - account_classes from its class name""" - self.__logger.debug("Looking for " + account_class_name) - for _account_class in self.account_classes: - if _account_class.__name__.lower() == account_class_name.lower(): - self.__logger.debug(account_class_name + " found") - return _account_class - self.__logger.debug(account_class_name + " not found") - return None - - # TODO : delete - def _send_presence_available(self, _account, show, lang_class): - """Send available presence to account's user and ask for password - if necessary""" - _account.default_lang_class = lang_class - old_status = _account.status - if show is None: - _account.status = account.ONLINE - else: - _account.status = show - self.stream.send(Presence(from_jid = _account.jid, \ - to_jid = _account.user_jid, \ - status = _account.status_msg, \ - show = show, \ - stanza_type = "available")) - if hasattr(_account, 'store_password') \ - and hasattr(_account, 'password') \ - and _account.store_password == False \ - and old_status == account.OFFLINE \ - and _account.password == None : - self.ask_password(_account, lang_class) - - def ask_password(self, _account, lang_class): - """Send a Jabber message to ask for account password - """ - if hasattr(_account, 'waiting_password_reply') \ - and not _account.waiting_password_reply \ - and _account.status != account.OFFLINE: - _account.waiting_password_reply = True - msg = Message(from_jid = _account.jid, \ - to_jid = _account.user_jid, \ - stanza_type = "normal", \ - subject = u"[PASSWORD] " + \ - lang_class.ask_password_subject, \ - body = lang_class.ask_password_body % \ - (_account.name)) - self.stream.send(msg) - def send_error(self, _account, exception): - """Send an error message only one time until _account.in_error - has been reset to False""" - if _account.in_error == False: - _account.in_error = True - msg = Message(from_jid = _account.jid, \ - to_jid = _account.user_jid, \ - stanza_type = "error", \ - subject = _account.default_lang_class.check_error_subject, \ - body = _account.default_lang_class.check_error_body \ - % (exception)) - self.stream.send(msg) + """""" + self.send_stanzas(self.account_manager.send_error(_account, exception)) type, value, stack = sys.exc_info() # TODO : not checking email here - self.__logger.debug("Error while first checking mail : %s\n%s" \ + self.__logger.debug("Error: %s\n%s" \ % (exception, "".join(traceback.format_exception (type, value, stack, 5)))) - def get_jid(self, _account): - """Return account jid based on account instance and component jid - """ - return _account.name + u"@" + unicode(self.jid) - ########################################################################### # Virtual methods ########################################################################### @@ -660,6 +537,7 @@ class AccountManager(object): disco_info.add_feature("jabber:iq:version") if not self.has_multiple_account_type: disco_info.add_feature("jabber:iq:register") + # TODO: name is always None DiscoIdentity(disco_info, name, \ category, \ type) @@ -891,21 +769,22 @@ class AccountManager(object): else: return [] - ###### presence_available handlers ###### - def account_handle_presence_available(self, name, from_jid, lang_class, show): - """Handle presence \"available\" sent to an account JID""" + + ###### 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(self._send_presence_available(accounts[0], show, lang_class)) + result.extend(presence_func(accounts[0])) self.db_disconnect() return result - - def root_handle_presence_available(self, from_jid, lang_class, show): - """Handle presence \"available\" sent to component JID""" + + 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(\ @@ -913,21 +792,128 @@ class AccountManager(object): accounts_length = 0 for _account in accounts: accounts_length += 1 - result.extend(self._send_presence_available(_account, \ - show, \ - lang_class)) + result.extend(presence_func(_account)) self.db_disconnect() if (accounts_length > 0): - result.append(Presence(from_jid = self.component.jid, \ - to_jid = from_jid, \ - status = \ - str(accounts_length) \ - + lang_class.message_status, \ - show = show, \ - stanza_type = "available")) + result.extend(root_presence_func(accounts_length)) return result + + def send_presence_all(self, presence): + """Send presence to all account. Optimized to use only one sql + request""" + current_user_jid = None + result = [] + self.db_connect() + # Explicit reference to account table (clauseTables) to use + # "user_jid" column with Account subclasses + for _account in \ + Account.select(clauseTables = ["account"], \ + orderBy = "user_jid"): + if current_user_jid != _account.user_jid: + current_user_jid = _account.user_jid + result.extend(getattr(self, "_send_presence_" + \ + presence + "_root")(_account.user_jid)) + result.extend(getattr(self, "_send_presence_" + \ + presence)(_account)) + self.db_disconnect() + 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""" + 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""" + return self.root_handle_presence(from_jid, \ + lambda _account:\ + self._send_presence_available(_account, \ + show, \ + lang_class), \ + lambda nb_accounts : \ + self._send_presence_available_root(from_jid, \ + 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""" + 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""" + return self.root_handle_presence(from_jid, \ + lambda _account:\ + self._send_presence_unavailable(_account), \ + lambda nb_accounts: \ + self._send_presence_unavailable_root(from_jid)) + + ###### 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 + ###### Utils methods ###### + def _has_account(self, from_jid, name = None): + """Check if user with \"from_jid\" JID has an account. + 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, disco_items, _account_class, bare_from_jid, account_type = ""): """List accounts in disco_items for given _account_class and user jid""" if account_type is not None and account_type != "": @@ -1030,6 +1016,33 @@ class AccountManager(object): """Compose account jid from account name""" return name + u"@" + unicode(self.component.jid) + def _send_presence_unavailable_root(self, to_jid): + """Send unavailable presence to account's user from root JID""" + result = [] + result.append(Presence(from_jid = self.component.jid, \ + to_jid = to_jid, \ + stanza_type = "unavailable")) + return result + + def _send_presence_unavailable(self, _account): + """Send unavailable presence to account's user""" + result = [] + _account.status = account.OFFLINE + result.append(Presence(from_jid = _account.jid, \ + to_jid = _account.user_jid, \ + stanza_type = "unavailable")) + return result + + def _send_presence_available_root(self, to_jid, show, status_msg): + """Send available presence to account's user from root JID""" + result = [] + result.append(Presence(from_jid = self.component.jid, \ + to_jid = to_jid, \ + status = status_msg, \ + show = show, \ + stanza_type = "available")) + return result + def _send_presence_available(self, _account, show, lang_class): """Send available presence to account's user and ask for password if necessary""" @@ -1069,3 +1082,17 @@ class AccountManager(object): body = lang_class.ask_password_body % \ (_account.name))) return result + + def send_error(self, _account, exception): + """Send an error message only one time until _account.in_error + has been reset to False""" + result = [] + if _account.in_error == False: + _account.in_error = True + result.append(Message(from_jid = _account.jid, \ + to_jid = _account.user_jid, \ + stanza_type = "error", \ + subject = _account.default_lang_class.check_error_subject, \ + body = _account.default_lang_class.check_error_body \ + % (exception))) + return result diff --git a/tests/jcl/jabber/test_component.py b/tests/jcl/jabber/test_component.py index dbe4ecc..218d1fd 100644 --- a/tests/jcl/jabber/test_component.py +++ b/tests/jcl/jabber/test_component.py @@ -246,32 +246,48 @@ class JCLComponent_TestCase(unittest.TestCase): self.assertTrue(self.comp.stream.connection_stopped) if self.comp.queue.qsize(): raise self.comp.queue.get(0) - self.assertEquals(len(self.comp.stream.sent), 5) - presence = self.comp.stream.sent[0] - self.assertTrue(isinstance(presence, Presence)) - self.assertEquals(presence.get_from(), "jcl.test.com") - self.assertEquals(presence.get_to(), "test1@test.com") - self.assertEquals(presence.get_node().prop("type"), "unavailable") - presence = self.comp.stream.sent[1] - self.assertTrue(isinstance(presence, Presence)) - self.assertEquals(presence.get_from(), "account11@jcl.test.com") - self.assertEquals(presence.get_to(), "test1@test.com") - self.assertEquals(presence.get_node().prop("type"), "unavailable") - presence = self.comp.stream.sent[2] - self.assertTrue(isinstance(presence, Presence)) - self.assertEquals(presence.get_from(), "account12@jcl.test.com") - self.assertEquals(presence.get_to(), "test1@test.com") - self.assertEquals(presence.get_node().prop("type"), "unavailable") - presence = self.comp.stream.sent[3] - self.assertTrue(isinstance(presence, Presence)) - self.assertEquals(presence.get_from(), "jcl.test.com") - self.assertEquals(presence.get_to(), "test2@test.com") - self.assertEquals(presence.get_node().prop("type"), "unavailable") - presence = self.comp.stream.sent[4] - self.assertTrue(isinstance(presence, Presence)) - self.assertEquals(presence.get_from(), "account2@jcl.test.com") - self.assertEquals(presence.get_to(), "test2@test.com") - self.assertEquals(presence.get_node().prop("type"), "unavailable") + presence_sent = self.comp.stream.sent + self.assertEqual(len(presence_sent), 5) + self.assertEqual(len([presence \ + for presence in presence_sent \ + if presence.get_to_jid() == "test1@test.com"]), \ + 3) + self.assertEqual(\ + len([presence \ + for presence in presence_sent \ + if presence.get_from_jid() == \ + "jcl.test.com" \ + and presence.xpath_eval("@type")[0].get_content() \ + == "unavailable"]), \ + 2) + self.assertEqual(\ + len([presence \ + for presence in presence_sent \ + if presence.get_from_jid() == \ + "account11@jcl.test.com" \ + and presence.xpath_eval("@type")[0].get_content() \ + == "unavailable"]), \ + 1) + self.assertEqual(\ + len([presence \ + for presence in presence_sent \ + if presence.get_from_jid() == \ + "account12@jcl.test.com" \ + and presence.xpath_eval("@type")[0].get_content() \ + == "unavailable"]), \ + 1) + self.assertEqual(len([presence \ + for presence in presence_sent \ + if presence.get_to_jid() == "test2@test.com"]), \ + 2) + self.assertEqual(\ + len([presence \ + for presence in presence_sent \ + if presence.get_from_jid() == \ + "account2@jcl.test.com" \ + and presence.xpath_eval("@type")[0].get_content() \ + == "unavailable"]), \ + 1) ########################################################################### # 'time_handler' tests