From 06129f45ef6adfb3403fc5a4bf8268bac8e77226 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Thu, 5 Nov 2009 15:57:43 +0100 Subject: [PATCH 01/19] Let contact instances know their corresponding account. contact.account and gc_contact.account contains the account name of the owning account. There is still code around in many placed which tries to workaround this missing information. Such code has to be migrated on per-need basis. --- src/common/contacts.py | 43 +++++++++++++++--------- src/dialogs.py | 2 +- src/filetransfers_window.py | 2 +- src/groupchat_control.py | 2 +- src/gui_interface.py | 16 ++++----- src/roster_window.py | 14 ++++---- src/search_window.py | 2 +- src/session.py | 2 +- test/unit/test_contacts.py | 67 +++++++++++++++++++++++++++++++++---- 9 files changed, 108 insertions(+), 42 deletions(-) diff --git a/src/common/contacts.py b/src/common/contacts.py index b50f80d70..ff42163a3 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -29,16 +29,23 @@ ## import common.gajim - - from common import caps - -class CommonContact(object): +class XMPPEntity(object): + '''Base representation of entities in XMPP''' - def __init__(self, jid, resource, show, status, name, our_chatstate, + def __init__(self, jid, account, resource): + self.jid = jid + self.resource = resource + self.account = account + +class CommonContact(XMPPEntity): + + def __init__(self, jid, account, resource, show, status, name, our_chatstate, composing_xep, chatstate, client_caps=None): + XMPPEntity.__init__(self, jid, account, resource) + self.jid = jid self.resource = resource self.show = show @@ -99,12 +106,12 @@ class CommonContact(object): class Contact(CommonContact): '''Information concerning each contact''' - def __init__(self, jid='', name='', groups=[], show='', status='', sub='', + def __init__(self, jid, account, name='', groups=[], show='', status='', sub='', ask='', resource='', priority=0, keyID='', client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None, msg_id = None, composing_xep=None, mood={}, tune={}, activity={}): - CommonContact.__init__(self, jid, resource, show, status, name, + CommonContact.__init__(self, jid, account, resource, show, status, name, our_chatstate, composing_xep, chatstate, client_caps=client_caps) self.contact_name = '' # nick choosen by contact @@ -184,11 +191,11 @@ class Contact(CommonContact): class GC_Contact(CommonContact): '''Information concerning each groupchat contact''' - def __init__(self, room_jid='', name='', show='', status='', role='', + def __init__(self, room_jid, account, name='', show='', status='', role='', affiliation='', jid='', resource='', our_chatstate=None, composing_xep=None, chatstate=None): - CommonContact.__init__(self, jid, resource, show, status, name, + CommonContact.__init__(self, jid, account, resource, show, status, name, our_chatstate, composing_xep, chatstate) self.room_jid = room_jid @@ -233,7 +240,7 @@ class Contacts: del self._gc_contacts[account] del self._metacontacts_tags[account] - def create_contact(self, jid='', name='', groups=[], show='', status='', + def create_contact(self, jid, account, name='', groups=[], show='', status='', sub='', ask='', resource='', priority=0, keyID='', client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None, composing_xep=None, mood={}, tune={}, activity={}): @@ -244,21 +251,23 @@ class Contacts: if group not in groups_unique: groups_unique.append(group) - return Contact(jid=jid, name=name, groups=groups_unique, show=show, - status=status, sub=sub, ask=ask, resource=resource, priority=priority, + return Contact(jid=jid, account=account, name=name, groups=groups_unique, + show=show, status=status, sub=sub, ask=ask, resource=resource, priority=priority, keyID=keyID, client_caps=client_caps, our_chatstate=our_chatstate, chatstate=chatstate, last_status_time=last_status_time, composing_xep=composing_xep, mood=mood, tune=tune, activity=activity) def copy_contact(self, contact): - return self.create_contact(jid=contact.jid, name=contact.name, - groups=contact.groups, show=contact.show, status=contact.status, + return self.create_contact(jid=contact.jid, account=contact.account, + name=contact.name, groups=contact.groups, show=contact.show, status=contact.status, sub=contact.sub, ask=contact.ask, resource=contact.resource, priority=contact.priority, keyID=contact.keyID, client_caps=contact.client_caps, our_chatstate=contact.our_chatstate, chatstate=contact.chatstate, last_status_time=contact.last_status_time) def add_contact(self, account, contact): + assert account == contact.account # migration check + # No such account before ? if account not in self._contacts: self._contacts[account] = {contact.jid : [contact]} @@ -623,16 +632,18 @@ class Contacts: def contact_from_gc_contact(self, gc_contact): '''Create a Contact instance from a GC_Contact instance''' jid = gc_contact.get_full_jid() - return Contact(jid=jid, resource=gc_contact.resource, + return Contact(jid=jid, account=gc_contact.account, resource=gc_contact.resource, name=gc_contact.name, groups=[], show=gc_contact.show, status=gc_contact.status, sub='none', client_caps=gc_contact.client_caps) - def create_gc_contact(self, room_jid='', name='', show='', status='', + def create_gc_contact(self, room_jid, account, name='', show='', status='', role='', affiliation='', jid='', resource=''): return GC_Contact(room_jid, name, show, status, role, affiliation, jid, resource) def add_gc_contact(self, account, gc_contact): + assert account == gc_contact.account # migration check + # No such account before ? if account not in self._gc_contacts: self._contacts[account] = {gc_contact.room_jid : {gc_contact.name: \ diff --git a/src/dialogs.py b/src/dialogs.py index 3233855ea..d9cf91328 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -1888,7 +1888,7 @@ class SubscriptionRequestWindow: if self.jid in gajim.interface.instances[self.account]['infos']: gajim.interface.instances[self.account]['infos'][self.jid].window.present() else: - contact = gajim.contacts.create_contact(jid=self.jid, name='', + contact = gajim.contacts.create_contact(jid=self.jid, account=self.account, name='', groups=[], show='', status='', sub='', ask='', resource='', priority=5, keyID='', our_chatstate=None, chatstate=None) gajim.interface.instances[self.account]['infos'][self.jid] = \ diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index 2b3642da7..cae4549e6 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -284,7 +284,7 @@ _('Connection with peer cannot be established.')) if contact.find('/') == -1: return (jid, resource) = contact.split('/', 1) - contact = gajim.contacts.create_contact(jid=jid, resource=resource) + contact = gajim.contacts.create_contact(jid=jid, account=account, resource=resource) file_name = os.path.split(file_path)[1] file_props = self.get_send_file_props(account, contact, file_path, file_name, file_desc) diff --git a/src/groupchat_control.py b/src/groupchat_control.py index aad08ff67..24097300f 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -1463,7 +1463,7 @@ class GroupchatControl(ChatControlBase): self.draw_all_roles() iter_ = model.append(role_iter, (None, nick, 'contact', name, None)) if not nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - gc_contact = gajim.contacts.create_gc_contact(room_jid=self.room_jid, + gc_contact = gajim.contacts.create_gc_contact(room_jid=self.room_jid, account=self.account, name=nick, show=show, status=status, role=role, affiliation=affiliation, jid=j, resource=resource) gajim.contacts.add_gc_contact(self.account, gc_contact) diff --git a/src/gui_interface.py b/src/gui_interface.py index 3f8a50036..8a153405d 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -344,7 +344,7 @@ class Interface: # Ignore offline presence of unknown self resource if new_show < 2: return - contact1 = gajim.contacts.create_contact(jid=ji, + contact1 = gajim.contacts.create_contact(jid=ji, account=account, name=gajim.nicks[account], groups=['self_contact'], show=array[1], status=status_message, sub='both', ask='none', priority=priority, keyID=keyID, resource=resource, @@ -612,8 +612,8 @@ class Interface: keyID = attached_keys[attached_keys.index(jid) + 1] name = jid.split('@', 1)[0] name = name.split('%', 1)[0] - contact1 = gajim.contacts.create_contact(jid=jid, name=name, - groups=[], show='online', status='online', + contact1 = gajim.contacts.create_contact(jid=jid, account=account, + name=name, groups=[], show='online', status='online', ask='to', resource=array[1], keyID=keyID) gajim.contacts.add_contact(account, contact1) self.roster.add_contact(jid, account) @@ -1213,8 +1213,8 @@ class Interface: if sub == 'remove': return # Add new contact to roster - contact = gajim.contacts.create_contact(jid=jid, name=name, - groups=groups, show='offline', sub=sub, ask=ask) + contact = gajim.contacts.create_contact(jid=jid, account=account, + name=name, groups=groups, show='offline', sub=sub, ask=ask) gajim.contacts.add_contact(account, contact) self.roster.add_contact(jid, account) else: @@ -1353,7 +1353,7 @@ class Interface: 'attached_gpg_keys').split() if jid in attached_keys: keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_contact(jid=jid, name='', + contact = gajim.contacts.create_contact(jid=jid, account=account, name='', groups=[_('Not in Roster')], show='not in roster', status='', sub='none', keyID=keyID) gajim.contacts.add_contact(account, contact) @@ -2567,7 +2567,7 @@ class Interface: account): # Join new groupchat if minimize: - contact = gajim.contacts.create_contact(jid=room_jid, name=nick) + contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick) gc_control = GroupchatControl(None, contact, account) gajim.interface.minimized_controls[account][room_jid] = gc_control self.roster.add_groupchat(room_jid, account) @@ -2590,7 +2590,7 @@ class Interface: def new_room(self, room_jid, nick, account, is_continued=False): # Get target window, create a control, and associate it with the window - contact = gajim.contacts.create_contact(jid=room_jid, name=nick) + contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick) mw = self.msg_win_mgr.get_window(contact.jid, account) if not mw: mw = self.msg_win_mgr.create_window(contact, account, diff --git a/src/roster_window.py b/src/roster_window.py index 9181c0e5b..a50021a6c 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -808,7 +808,7 @@ class RosterWindow: else: name = jid.split('@')[0] # New groupchat - contact = gajim.contacts.create_contact(jid=jid, name=name, + contact = gajim.contacts.create_contact(jid=jid, account=account, name=name, groups=[_('Groupchats')], show=show, status=status, sub='none') gajim.contacts.add_contact(account, contact) self.add_contact(jid, account) @@ -843,7 +843,7 @@ class RosterWindow: Return the added contact instance.''' contact = gajim.contacts.get_contact_with_highest_priority(account, jid) if contact is None: - contact = gajim.contacts.create_contact(jid=jid, name=jid, + contact = gajim.contacts.create_contact(jid=jid, account=account, name=jid, groups=[_('Transports')], show='offline', status='offline', sub='from') gajim.contacts.add_contact(account, contact) @@ -984,7 +984,7 @@ class RosterWindow: 'attached_gpg_keys').split() if jid in attached_keys: keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_contact(jid=jid, name=nick, + contact = gajim.contacts.create_contact(jid=jid, account=account, name=nick, groups=[_('Not in Roster')], show='not in roster', status='', sub='none', resource=resource, keyID=keyID) gajim.contacts.add_contact(account, contact) @@ -1774,7 +1774,7 @@ class RosterWindow: if gajim.jid_is_transport(jid): array[jid]['groups'] = [_('Transports')] - contact1 = gajim.contacts.create_contact(jid=ji, name=name, + contact1 = gajim.contacts.create_contact(jid=ji, account=account, name=name, groups=array[jid]['groups'], show=show, status=status, sub=array[jid]['subscription'], ask=array[jid]['ask'], resource=resource, keyID=keyID) @@ -1921,7 +1921,7 @@ class RosterWindow: 'attached_gpg_keys').split() if jid in attached_keys: keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_contact(jid=jid, name=nickname, + contact = gajim.contacts.create_contact(jid=jid, account=account, name=nickname, groups=groups, show='requested', status='', ask='none', sub='subscribe', keyID=keyID) gajim.contacts.add_contact(account, contact) @@ -2511,7 +2511,7 @@ class RosterWindow: account_name = account if gajim.account_is_connected(account): account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - contact = gajim.contacts.create_contact(jid=jid, name=account_name, + contact = gajim.contacts.create_contact(jid=jid, account=account, name=account_name, show=connection.get_status(), sub='', status=connection.status, resource=connection.server_resource, priority=connection.priority, mood=connection.mood, @@ -5102,7 +5102,7 @@ class RosterWindow: service_discovery_menuitem.connect('activate', self.on_service_disco_menuitem_activate, account) hostname = gajim.config.get_per('accounts', account, 'hostname') - contact = gajim.contacts.create_contact(jid=hostname) # Fake contact + contact = gajim.contacts.create_contact(jid=hostname, account=account) # Fake contact execute_command_menuitem.connect('activate', self.on_execute_command, contact, account) diff --git a/src/search_window.py b/src/search_window.py index 3f7f40bd0..e383e7c4f 100644 --- a/src/search_window.py +++ b/src/search_window.py @@ -110,7 +110,7 @@ class SearchWindow: if jid in gajim.interface.instances[self.account]['infos']: gajim.interface.instances[self.account]['infos'][jid].window.present() else: - contact = gajim.contacts.create_contact(jid = jid, name='', groups=[], + contact = gajim.contacts.create_contact(jid=jid, account=self.account, name='', groups=[], show='', status='', sub='', ask='', resource='', priority=0, keyID='', our_chatstate=None, chatstate=None) gajim.interface.instances[self.account]['infos'][jid] = \ diff --git a/src/session.py b/src/session.py index dec454c18..9143e22a2 100644 --- a/src/session.py +++ b/src/session.py @@ -506,7 +506,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): contact = gajim.contacts.get_contact(account, self.jid, resource) if not contact: - contact = gajim.contacts.create_contact(jid=jid, + contact = gajim.contacts.create_contact(jid=jid, account=account, resource=resource, show=self.conn.get_status()) gajim.interface.new_chat(contact, account, resource=resource, diff --git a/test/unit/test_contacts.py b/test/unit/test_contacts.py index 401f35620..623fec1ad 100644 --- a/test/unit/test_contacts.py +++ b/test/unit/test_contacts.py @@ -6,7 +6,7 @@ import unittest import lib lib.setup_env() -from common.contacts import CommonContact, Contact, GC_Contact +from common.contacts import CommonContact, Contact, GC_Contact, Contacts from common.xmpp import NS_MUC from common import caps @@ -14,9 +14,9 @@ from common import caps class TestCommonContact(unittest.TestCase): def setUp(self): - self.contact = CommonContact(jid='', resource='', show='', status='', - name='', our_chatstate=None, composing_xep=None, chatstate=None, - client_caps=None) + self.contact = CommonContact(jid='', account="", resource='', show='', + status='', name='', our_chatstate=None, composing_xep=None, + chatstate=None, client_caps=None) def test_default_client_supports(self): ''' @@ -31,21 +31,76 @@ class TestCommonContact(unittest.TestCase): self.assertTrue(self.contact.supports(NS_MUC), msg="Must not backtrace on simple check for supported feature") + + class TestContact(TestCommonContact): def setUp(self): TestCommonContact.setUp(self) - self.contact = Contact() + self.contact = Contact(jid="test@gajim.org", account="account") + def test_attributes_available(self): + '''This test supports the migration from the old to the new contact + domain model by smoke testing that no attribute values are lost''' + + attributes = ["jid", "resource", "show", "status", "name", "our_chatstate", + "composing_xep", "chatstate", "client_caps", "priority", "sub"] + for attr in attributes: + self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) + class TestGC_Contact(TestCommonContact): def setUp(self): TestCommonContact.setUp(self) - self.contact = GC_Contact() + self.contact = GC_Contact("confernce@gajim.org", "account") + + def test_attributes_available(self): + '''This test supports the migration from the old to the new contact + domain model by asserting no attributes have been lost''' + + attributes = ["jid", "resource", "show", "status", "name", "our_chatstate", + "composing_xep", "chatstate", "client_caps", "role", "room_jid"] + for attr in attributes: + self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) +class TestContacts(unittest.TestCase): + + def setUp(self): + self.contacts = Contacts() + + def test_create_add_get_contact(self): + jid = 'test@gajim.org' + account = "account" + + contact = self.contacts.create_contact(jid=jid, account=account) + self.contacts.add_contact(account, contact) + + retrieved_contact = self.contacts.get_contact(account, jid) + self.assertEqual(contact, retrieved_contact, "Contact must be known") + + self.contacts.remove_contact(account, contact) + + retrieved_contact = self.contacts.get_contact(account, jid) + self.assertNotEqual(contact, retrieved_contact, + msg="Contact must not be known any longer") + + + def test_copy_contact(self): + jid = 'test@gajim.org' + account = "account" + + contact = self.contacts.create_contact(jid=jid, account=account) + copy = self.contacts.copy_contact(contact) + self.assertFalse(contact is copy, msg="Must not be the same") + + # Not yet implemented to remain backwart compatible + # self.assertEqual(contact, copy, msg="Must be equal") + + + if __name__ == "__main__": unittest.main() \ No newline at end of file From dec25246dfa40f97200880ccc7000d7afd249553 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Thu, 5 Nov 2009 16:22:21 +0100 Subject: [PATCH 02/19] Missing bits of the last commit (add account parameter to contact constructor) --- src/roster_window.py | 2 +- test/integration/test_roster.py | 2 +- test/unit/test_contacts.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/roster_window.py b/src/roster_window.py index a50021a6c..8bb0e0288 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -2540,7 +2540,7 @@ class RosterWindow: show = roster.getShow(jid+'/'+resource) if not show: show = 'online' - contact = gajim.contacts.create_contact(jid=jid, + contact = gajim.contacts.create_contact(jid=jid, account=account, name=account, groups=['self_contact'], show=show, status=roster.getStatus(jid + '/' + resource), resource=resource, diff --git a/test/integration/test_roster.py b/test/integration/test_roster.py index 10d1ab488..d093d0e64 100644 --- a/test/integration/test_roster.py +++ b/test/integration/test_roster.py @@ -190,7 +190,7 @@ class TestRosterWindowMetaContacts(TestRosterWindowRegrouped): self.test_fill_roster_model() jid = u'coolstuff@gajim.org' - contact = gajim.contacts.create_contact(jid) + contact = gajim.contacts.create_contact(jid, account1) gajim.contacts.add_contact(account1, contact) self.roster.add_contact(jid, account1) self.roster.chg_contact_status(contact, 'offline', '', account1) diff --git a/test/unit/test_contacts.py b/test/unit/test_contacts.py index 623fec1ad..2ec1dd8c4 100644 --- a/test/unit/test_contacts.py +++ b/test/unit/test_contacts.py @@ -55,7 +55,7 @@ class TestGC_Contact(TestCommonContact): def setUp(self): TestCommonContact.setUp(self) - self.contact = GC_Contact("confernce@gajim.org", "account") + self.contact = GC_Contact(room_jid="confernce@gajim.org", account="account") def test_attributes_available(self): '''This test supports the migration from the old to the new contact From 89b1c6a7df3558b1575245f30168e2222a8e5411 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Thu, 5 Nov 2009 16:25:13 +0100 Subject: [PATCH 03/19] Move method close to data. gajim.contacts.contact_from_gc_contact(gc_contact) is now gc_contact.as_contact() --- src/chat_control.py | 2 +- src/common/contacts.py | 13 ++++++------- src/groupchat_control.py | 14 +++++++------- src/gui_interface.py | 10 +++++----- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/chat_control.py b/src/chat_control.py index 93aa01576..da028f618 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -2750,7 +2750,7 @@ class ChatControl(ChatControlBase): contact = gajim.contacts.get_contact_with_highest_priority( self.account, self.contact.jid) if isinstance(contact, GC_Contact): - contact = gajim.contacts.contact_from_gc_contact(contact) + contact = contact.as_contact() if contact: self.contact = contact self.draw_banner() diff --git a/src/common/contacts.py b/src/common/contacts.py index ff42163a3..35a9f39b5 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -208,6 +208,12 @@ class GC_Contact(CommonContact): def get_shown_name(self): return self.name + def as_contact(self): + '''Create a Contact instance from this GC_Contact instance''' + return Contact(jid=self.get_full_jid(), account=self.account, + resource=self.resource, name=self.name, groups=[], show=self.show, + status=self.status, sub='none', client_caps=self.client_caps) + class Contacts: '''Information concerning all contacts and groupchat contacts''' @@ -629,13 +635,6 @@ class Contacts: def get_jid_list(self, account): return self._contacts[account].keys() - def contact_from_gc_contact(self, gc_contact): - '''Create a Contact instance from a GC_Contact instance''' - jid = gc_contact.get_full_jid() - return Contact(jid=jid, account=gc_contact.account, resource=gc_contact.resource, - name=gc_contact.name, groups=[], show=gc_contact.show, - status=gc_contact.status, sub='none', client_caps=gc_contact.client_caps) - def create_gc_contact(self, room_jid, account, name='', show='', status='', role='', affiliation='', jid='', resource=''): return GC_Contact(room_jid, name, show, status, role, affiliation, jid, diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 24097300f..e71eefe32 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -176,7 +176,7 @@ class PrivateChatControl(ChatControl): ChatControl.update_ui(self) def update_contact(self): - self.contact = gajim.contacts.contact_from_gc_contact(self.gc_contact) + self.contact = self.gc_contact.as_contact() def begin_e2e_negotiation(self): self.no_autonegotiation = True @@ -2254,14 +2254,14 @@ class GroupchatControl(ChatControlBase): def on_info(self, widget, nick): '''Call vcard_information_window class to display user's information''' - c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) - c2 = gajim.contacts.contact_from_gc_contact(c) - if c2.jid in gajim.interface.instances[self.account]['infos']: - gajim.interface.instances[self.account]['infos'][c2.jid].window.\ + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) + contact = gc_contact.as_contact() + if contact.jid in gajim.interface.instances[self.account]['infos']: + gajim.interface.instances[self.account]['infos'][contact.jid].window.\ present() else: - gajim.interface.instances[self.account]['infos'][c2.jid] = \ - vcard.VcardWindow(c2, self.account, c) + gajim.interface.instances[self.account]['infos'][contact.jid] = \ + vcard.VcardWindow(contact, self.account, gc_contact) def on_history(self, widget, nick): jid = gajim.construct_fjid(self.room_jid, nick) diff --git a/src/gui_interface.py b/src/gui_interface.py index 8a153405d..179a9c02b 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -533,8 +533,8 @@ class Interface: show = model[iter_][3] else: show = 'offline' - gc_c = gajim.contacts.create_gc_contact(room_jid = jid, - name = nick, show = show) + gc_c = gajim.contacts.create_gc_contact(room_jid=jid, account=account, + name=nick, show=show) ctrl = self.new_private_chat(gc_c, account, session) ctrl.print_conversation(_('Error %(code)s: %(msg)s') % { @@ -890,7 +890,7 @@ class Interface: ctrl.print_conversation(_('%(nick)s is now known as %(new_nick)s') \ % {'nick': nick, 'new_nick': new_nick}, 'status') gc_c = gajim.contacts.get_gc_contact(account, room_jid, new_nick) - c = gajim.contacts.contact_from_gc_contact(gc_c) + c = gc_c.as_contact() ctrl.gc_contact = gc_c ctrl.contact = c if ctrl.session: @@ -2223,7 +2223,7 @@ class Interface: else: show = 'offline' gc_contact = gajim.contacts.create_gc_contact( - room_jid = room_jid, name = nick, show = show) + room_jid=room_jid, account=account, name=nick, show=show) if not session: session = gajim.connections[account].make_new_session( @@ -2600,7 +2600,7 @@ class Interface: mw.new_tab(gc_control) def new_private_chat(self, gc_contact, account, session=None): - contact = gajim.contacts.contact_from_gc_contact(gc_contact) + contact = gc_contact.as_contact() type_ = message_control.TYPE_PM fjid = gc_contact.room_jid + '/' + gc_contact.name From 8a127b4e4dcfc1f2268c26466753c26f4d9194a2 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Thu, 5 Nov 2009 16:53:30 +0100 Subject: [PATCH 04/19] Several small bugfixes: Add missing accout parameter to constructors. --- src/common/contacts.py | 4 +--- src/session.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/common/contacts.py b/src/common/contacts.py index 35a9f39b5..19ffaa76f 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -46,8 +46,6 @@ class CommonContact(XMPPEntity): XMPPEntity.__init__(self, jid, account, resource) - self.jid = jid - self.resource = resource self.show = show self.status = status self.name = name @@ -637,7 +635,7 @@ class Contacts: def create_gc_contact(self, room_jid, account, name='', show='', status='', role='', affiliation='', jid='', resource=''): - return GC_Contact(room_jid, name, show, status, role, affiliation, jid, + return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid, resource) def add_gc_contact(self, account, gc_contact): diff --git a/src/session.py b/src/session.py index 9143e22a2..6438c518e 100644 --- a/src/session.py +++ b/src/session.py @@ -203,7 +203,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): if not contact: # contact is not in the roster, create a fake one to display # notification - contact = contacts.Contact(jid=jid, resource=resource) + contact = gajim.contacts.create_contact(jid=jid, account=self.conn.name, resource=resource) advanced_notif_num = notify.get_advanced_notification('message_received', self.conn.name, contact) From 7829e7d40c951fb65fa7a927750d322e1e9428b9 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Thu, 5 Nov 2009 17:05:13 +0100 Subject: [PATCH 05/19] Removed unused module 'meta.py' --- src/common/meta.py | 36 ------------------------------------ test/unit/test_caps.py | 2 +- 2 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 src/common/meta.py diff --git a/src/common/meta.py b/src/common/meta.py deleted file mode 100644 index 153c2e952..000000000 --- a/src/common/meta.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/python - -import types - -class VerboseClassType(type): - indent = '' - - def __init__(cls, name, bases, dict): - super(VerboseClassType, cls).__init__(cls, name, bases, dict) - new = {} - print 'Initializing new class %s:' % cls - for fname, fun in dict.iteritems(): - wrap = hasattr(fun, '__call__') - print '%s%s is %s, we %s wrap it.' % \ - (cls.__class__.indent, fname, fun, wrap and 'will' or "won't") - if not wrap: continue - setattr(cls, fname, cls.wrap(name, fname, fun)) - - def wrap(cls, name, fname, fun): - def verbose(*a, **b): - args = ', '.join(map(repr, a)+map(lambda x:'%s=%r'%x, b.iteritems())) - print '%s%s.%s(%s):' % (cls.__class__.indent, name, fname, args) - cls.__class__.indent += '| ' - r = fun(*a, **b) - cls.__class__.indent = cls.__class__.indent[:-4] - print '%s+=%r' % (cls.__class__.indent, r) - return r - verbose.__name__ = fname - return verbose - -def nested_property(f): - ret = f() - p = {} - for v in ('fget', 'fset', 'fdel', 'doc'): - if v in ret: p[v]=ret[v] - return property(**p) diff --git a/test/unit/test_caps.py b/test/unit/test_caps.py index 787047c1f..9ac466c47 100644 --- a/test/unit/test_caps.py +++ b/test/unit/test_caps.py @@ -113,7 +113,7 @@ class TestClientCaps(CommonCapsTest): "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=") def test_client_supports(self): - contact = Contact(client_caps=self.client_caps) + contact = Contact(jid=None, account=None, client_caps=self.client_caps) self.assertTrue(contact.supports(NS_PING), msg="Assume supported, if we don't have caps") From 0abb1dfd200a18ae3e5e5782bab54de6eb5afb1a Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Thu, 5 Nov 2009 19:43:25 +0100 Subject: [PATCH 06/19] Extract class: MetacontactManager from Contacts For easy migration, interface is still preserved. --- src/common/contacts.py | 252 ++++++++++++++++++++++--------------- test/unit/test_contacts.py | 38 +++++- 2 files changed, 191 insertions(+), 99 deletions(-) diff --git a/src/common/contacts.py b/src/common/contacts.py index 19ffaa76f..e71cfe585 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -219,22 +219,21 @@ class Contacts: self._contacts = {} # list of contacts {acct: {jid1: [C1, C2]}, } one Contact per resource self._gc_contacts = {} # list of contacts that are in gc {acct: {room_jid: {nick: C}}} - # For meta contacts: - self._metacontacts_tags = {} - + self._metacontact_manager = MetacontactManager(); + def change_account_name(self, old_name, new_name): self._contacts[new_name] = self._contacts[old_name] self._gc_contacts[new_name] = self._gc_contacts[old_name] - self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name] del self._contacts[old_name] del self._gc_contacts[old_name] - del self._metacontacts_tags[old_name] + + self._metacontact_manager.change_account_name(old_name, new_name) def add_account(self, account): self._contacts[account] = {} self._gc_contacts[account] = {} - if account not in self._metacontacts_tags: - self._metacontacts_tags[account] = {} + + self._metacontact_manager.add_account(account) def get_accounts(self): return self._contacts.keys() @@ -242,7 +241,8 @@ class Contacts: def remove_account(self, account): del self._contacts[account] del self._gc_contacts[account] - del self._metacontacts_tags[account] + + self._metacontact_manager.remove_account(account) def create_contact(self, jid, account, name='', groups=[], show='', status='', sub='', ask='', resource='', priority=0, keyID='', client_caps=None, @@ -314,8 +314,7 @@ class Contacts: return del self._contacts[account][jid] if remove_meta: - # remove metacontacts info - self.remove_metacontact(account, jid) + self._metacontact_manager.remove_metacontact(account, jid) def get_contacts(self, account, jid): '''Returns the list of contact instances for this jid.''' @@ -421,11 +420,154 @@ class Contacts: nbr_total += 1 return nbr_online, nbr_total + def define_metacontacts(self, account, tags_list): + self._metacontact_manager.define_metacontacts(account, tags_list) + + def get_new_metacontacts_tag(self, jid): + self._metacontact_manager.get_new_metacontacts_tag(jid) + + def get_metacontacts_tags(self, account): + self._metacontact_manager.get_metacontacts_tags(account) + + def get_metacontacts_tag(self, account, jid): + self._metacontact_manager.get_metacontacts_tag(account, jid) + + def add_metacontact(self, brother_account, brother_jid, account, jid, order=None): + self._metacontact_manager.add_metacontact(brother_account, brother_jid, account, jid, order) + + def remove_metacontact(self, account, jid): + self._metacontact_manager.remove_metacontact(account, jid) + + def has_brother(self, account, jid, accounts): + self._metacontact_manager.has_brother(account, jid, accounts) + + def is_big_brother(self, account, jid, accounts): + self._metacontact_manager.is_big_brother(account, jid, accounts) + + def get_metacontacts_jids(self, tag, accounts): + self._metacontact_manager.get_metacontacts_jids(tag, accounts) + + def get_metacontacts_family(self, account, jid): + self._metacontact_manager.get_metacontacts_family(account, jid) + + def get_metacontacts_family_from_tag(self, account, tag): + self._metacontact_manager.get_metacontacts_family_from_tag(account, tag) + + def compare_metacontacts(self, data1, data2): + self._metacontact_manager.compare_metacontacts(data1, data2) + + def get_metacontacts_big_brother(self, family): + self._metacontact_manager.get_metacontacts_big_brother(family) + + def is_pm_from_jid(self, account, jid): + '''Returns True if the given jid is a private message jid''' + if jid in self._contacts[account]: + return False + return True + + def is_pm_from_contact(self, account, contact): + '''Returns True if the given contact is a private message contact''' + if isinstance(contact, Contact): + return False + return True + + def get_jid_list(self, account): + return self._contacts[account].keys() + + def create_gc_contact(self, room_jid, account, name='', show='', status='', + role='', affiliation='', jid='', resource=''): + return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid, + resource) + + def add_gc_contact(self, account, gc_contact): + assert account == gc_contact.account # migration check + + # No such account before ? + if account not in self._gc_contacts: + self._contacts[account] = {gc_contact.room_jid : {gc_contact.name: \ + gc_contact}} + return + # No such room_jid before ? + if gc_contact.room_jid not in self._gc_contacts[account]: + self._gc_contacts[account][gc_contact.room_jid] = {gc_contact.name: \ + gc_contact} + return + self._gc_contacts[account][gc_contact.room_jid][gc_contact.name] = \ + gc_contact + + def remove_gc_contact(self, account, gc_contact): + if account not in self._gc_contacts: + return + if gc_contact.room_jid not in self._gc_contacts[account]: + return + if gc_contact.name not in self._gc_contacts[account][ + gc_contact.room_jid]: + return + del self._gc_contacts[account][gc_contact.room_jid][gc_contact.name] + # It was the last nick in room ? + if not len(self._gc_contacts[account][gc_contact.room_jid]): + del self._gc_contacts[account][gc_contact.room_jid] + + def remove_room(self, account, room_jid): + if account not in self._gc_contacts: + return + if room_jid not in self._gc_contacts[account]: + return + del self._gc_contacts[account][room_jid] + + def get_gc_list(self, account): + if account not in self._gc_contacts: + return [] + return self._gc_contacts[account].keys() + + def get_nick_list(self, account, room_jid): + gc_list = self.get_gc_list(account) + if not room_jid in gc_list: + return [] + return self._gc_contacts[account][room_jid].keys() + + def get_gc_contact(self, account, room_jid, nick): + nick_list = self.get_nick_list(account, room_jid) + if not nick in nick_list: + return None + return self._gc_contacts[account][room_jid][nick] + + def get_nb_role_total_gc_contacts(self, account, room_jid, role): + '''Returns the number of group chat contacts for the given role and the + total number of group chat contacts''' + if account not in self._gc_contacts: + return 0, 0 + if room_jid not in self._gc_contacts[account]: + return 0, 0 + nb_role = nb_total = 0 + for nick in self._gc_contacts[account][room_jid]: + if self._gc_contacts[account][room_jid][nick].role == role: + nb_role += 1 + nb_total += 1 + return nb_role, nb_total + + +class MetacontactManager(): + + def __init__(self): + self._metacontacts_tags = {} + + def change_account_name(self, old_name, new_name): + self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name] + del self._metacontacts_tags[old_name] + + def add_account(self, account): + if account not in self._metacontacts_tags: + self._metacontacts_tags[account] = {} + + def remove_account(self, account): + del self._metacontacts_tags[account] + def define_metacontacts(self, account, tags_list): self._metacontacts_tags[account] = tags_list def get_new_metacontacts_tag(self, jid): - if not jid in self._metacontacts_tags.keys(): + if not jid in self._metacontacts_tags: return jid #FIXME: can this append ? assert False @@ -617,91 +759,5 @@ class Contacts: others will be ?''' family.sort(cmp=self.compare_metacontacts) return family[-1] - - def is_pm_from_jid(self, account, jid): - '''Returns True if the given jid is a private message jid''' - if jid in self._contacts[account]: - return False - return True - - def is_pm_from_contact(self, account, contact): - '''Returns True if the given contact is a private message contact''' - if isinstance(contact, Contact): - return False - return True - - def get_jid_list(self, account): - return self._contacts[account].keys() - - def create_gc_contact(self, room_jid, account, name='', show='', status='', - role='', affiliation='', jid='', resource=''): - return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid, - resource) - - def add_gc_contact(self, account, gc_contact): - assert account == gc_contact.account # migration check - - # No such account before ? - if account not in self._gc_contacts: - self._contacts[account] = {gc_contact.room_jid : {gc_contact.name: \ - gc_contact}} - return - # No such room_jid before ? - if gc_contact.room_jid not in self._gc_contacts[account]: - self._gc_contacts[account][gc_contact.room_jid] = {gc_contact.name: \ - gc_contact} - return - self._gc_contacts[account][gc_contact.room_jid][gc_contact.name] = \ - gc_contact - - def remove_gc_contact(self, account, gc_contact): - if account not in self._gc_contacts: - return - if gc_contact.room_jid not in self._gc_contacts[account]: - return - if gc_contact.name not in self._gc_contacts[account][ - gc_contact.room_jid]: - return - del self._gc_contacts[account][gc_contact.room_jid][gc_contact.name] - # It was the last nick in room ? - if not len(self._gc_contacts[account][gc_contact.room_jid]): - del self._gc_contacts[account][gc_contact.room_jid] - - def remove_room(self, account, room_jid): - if account not in self._gc_contacts: - return - if room_jid not in self._gc_contacts[account]: - return - del self._gc_contacts[account][room_jid] - - def get_gc_list(self, account): - if account not in self._gc_contacts: - return [] - return self._gc_contacts[account].keys() - - def get_nick_list(self, account, room_jid): - gc_list = self.get_gc_list(account) - if not room_jid in gc_list: - return [] - return self._gc_contacts[account][room_jid].keys() - - def get_gc_contact(self, account, room_jid, nick): - nick_list = self.get_nick_list(account, room_jid) - if not nick in nick_list: - return None - return self._gc_contacts[account][room_jid][nick] - - def get_nb_role_total_gc_contacts(self, account, room_jid, role): - '''Returns the number of group chat contacts for the given role and the - total number of group chat contacts''' - if account not in self._gc_contacts: - return 0, 0 - if room_jid not in self._gc_contacts[account]: - return 0, 0 - nb_role = nb_total = 0 - for nick in self._gc_contacts[account][room_jid]: - if self._gc_contacts[account][room_jid][nick].role == role: - nb_role += 1 - nb_total += 1 - return nb_role, nb_total + # vim: se ts=3: diff --git a/test/unit/test_contacts.py b/test/unit/test_contacts.py index 2ec1dd8c4..b1655a0e3 100644 --- a/test/unit/test_contacts.py +++ b/test/unit/test_contacts.py @@ -5,6 +5,7 @@ import unittest import lib lib.setup_env() +from mock import Mock from common.contacts import CommonContact, Contact, GC_Contact, Contacts from common.xmpp import NS_MUC @@ -99,8 +100,43 @@ class TestContacts(unittest.TestCase): # Not yet implemented to remain backwart compatible # self.assertEqual(contact, copy, msg="Must be equal") - + + # AUto generated tests. Can be dropped after migration + + + def test_creation(self): + contacts = Contacts() +#Makesureitdoesn'traiseanyexceptions. + + def test_add_account_3_times(self): + contacts = Contacts() + + from common import gajim + gajim.connections[u'dingdong.org'] = Mock() + gajim.connections[u'Cool"ch\xe2r\xdf\xe9\xb5\xf6'] = Mock() + gajim.connections[u'acc1'] = Mock() + + self.assertEqual(None, contacts.add_account(account=u'acc1')) + self.assertEqual(None, contacts.add_account(account=u'Cool"ch\xe2r\xdf\xe9\xb5\xf6')) + self.assertEqual(None, contacts.add_account(account=u'dingdong.org')) + + def test_add_metacontact_4_times_and_define_metacontacts_3_times(self): + contacts = Contacts() + + from common import gajim + gajim.connections[u'dingdong.org'] = Mock() + gajim.connections[u'Cool"ch\xe2r\xdf\xe9\xb5\xf6'] = Mock() + gajim.connections[u'acc1'] = Mock() + + self.assertEqual(None, contacts.define_metacontacts(account=u'acc1', tags_list={})) + self.assertEqual(None, contacts.define_metacontacts(account=u'Cool"ch\xe2r\xdf\xe9\xb5\xf6', tags_list={})) + self.assertEqual(None, contacts.define_metacontacts(account=u'dingdong.org', tags_list={})) + self.assertEqual(None, contacts.add_metacontact(account=u'dingdong.org', brother_account=u'dingdong.org', brother_jid=u'guypsych0\\40h.com@msn.dingdong.org', jid=u'guypsych0%h.com@msn.delx.cjb.net', order=None)) + self.assertEqual(None, contacts.add_metacontact(account=u'dingdong.org', brother_account=u'dingdong.org', brother_jid=u'guypsych0\\40h.com@msn.dingdong.org', jid=u'guypsych0%h.com@msn.jabber.wiretrip.org', order=None)) + self.assertEqual(None, contacts.add_metacontact(account=u'dingdong.org', brother_account=u'dingdong.org', brother_jid=u'guypsych0\\40h.com@msn.dingdong.org', jid=u'guypsycho\\40g.com@gtalk.dingdong.org', order=None)) + self.assertEqual(None, contacts.add_metacontact(account=u'Cool"ch\xe2r\xdf\xe9\xb5\xf6', brother_account=u'acc1', brother_jid=u'samejid@gajim.org', jid=u'samejid@gajim.org', order=None)) + if __name__ == "__main__": unittest.main() \ No newline at end of file From f297aa0a116e86fa62f945acfcb75b962d346efa Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Thu, 5 Nov 2009 20:09:26 +0100 Subject: [PATCH 07/19] Make methods of MetacontactManager private if those are only used internally --- src/common/contacts.py | 61 +++++++++++++++++++----------------------- src/roster_window.py | 4 +-- 2 files changed, 29 insertions(+), 36 deletions(-) diff --git a/src/common/contacts.py b/src/common/contacts.py index e71cfe585..a151e216c 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -219,7 +219,7 @@ class Contacts: self._contacts = {} # list of contacts {acct: {jid1: [C1, C2]}, } one Contact per resource self._gc_contacts = {} # list of contacts that are in gc {acct: {room_jid: {nick: C}}} - self._metacontact_manager = MetacontactManager(); + self._metacontact_manager = MetacontactManager(self); def change_account_name(self, old_name, new_name): self._contacts[new_name] = self._contacts[old_name] @@ -421,43 +421,34 @@ class Contacts: return nbr_online, nbr_total def define_metacontacts(self, account, tags_list): - self._metacontact_manager.define_metacontacts(account, tags_list) - - def get_new_metacontacts_tag(self, jid): - self._metacontact_manager.get_new_metacontacts_tag(jid) + return self._metacontact_manager.define_metacontacts(account, tags_list) def get_metacontacts_tags(self, account): - self._metacontact_manager.get_metacontacts_tags(account) - - def get_metacontacts_tag(self, account, jid): - self._metacontact_manager.get_metacontacts_tag(account, jid) + return self._metacontact_manager.get_metacontacts_tags(account) def add_metacontact(self, brother_account, brother_jid, account, jid, order=None): - self._metacontact_manager.add_metacontact(brother_account, brother_jid, account, jid, order) + return self._metacontact_manager.add_metacontact(brother_account, brother_jid, account, jid, order) def remove_metacontact(self, account, jid): - self._metacontact_manager.remove_metacontact(account, jid) + return self._metacontact_manager.remove_metacontact(account, jid) def has_brother(self, account, jid, accounts): - self._metacontact_manager.has_brother(account, jid, accounts) + return self._metacontact_manager.has_brother(account, jid, accounts) def is_big_brother(self, account, jid, accounts): - self._metacontact_manager.is_big_brother(account, jid, accounts) + return self._metacontact_manager.is_big_brother(account, jid, accounts) def get_metacontacts_jids(self, tag, accounts): - self._metacontact_manager.get_metacontacts_jids(tag, accounts) + return self._metacontact_manager.get_metacontacts_jids(tag, accounts) def get_metacontacts_family(self, account, jid): - self._metacontact_manager.get_metacontacts_family(account, jid) + return self._metacontact_manager.get_metacontacts_family(account, jid) def get_metacontacts_family_from_tag(self, account, tag): - self._metacontact_manager.get_metacontacts_family_from_tag(account, tag) - - def compare_metacontacts(self, data1, data2): - self._metacontact_manager.compare_metacontacts(data1, data2) + return self._metacontact_manager.get_metacontacts_family_from_tag(account, tag) def get_metacontacts_big_brother(self, family): - self._metacontact_manager.get_metacontacts_big_brother(family) + return self._metacontact_manager.get_metacontacts_big_brother(family) def is_pm_from_jid(self, account, jid): '''Returns True if the given jid is a private message jid''' @@ -549,8 +540,9 @@ class Contacts: class MetacontactManager(): - def __init__(self): + def __init__(self, contacts): self._metacontacts_tags = {} + self._contacts = contacts def change_account_name(self, old_name, new_name): self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name] @@ -566,7 +558,7 @@ class MetacontactManager(): def define_metacontacts(self, account, tags_list): self._metacontacts_tags[account] = tags_list - def get_new_metacontacts_tag(self, jid): + def _get_new_metacontacts_tag(self, jid): if not jid in self._metacontacts_tags: return jid #FIXME: can this append ? @@ -578,7 +570,7 @@ class MetacontactManager(): return [] return self._metacontacts_tags[account].keys() - def get_metacontacts_tag(self, account, jid): + def _get_metacontacts_tag(self, account, jid): '''Returns the tag of a jid''' if not account in self._metacontacts_tags: return None @@ -589,19 +581,19 @@ class MetacontactManager(): return None def add_metacontact(self, brother_account, brother_jid, account, jid, order=None): - tag = self.get_metacontacts_tag(brother_account, brother_jid) + tag = self._get_metacontacts_tag(brother_account, brother_jid) if not tag: - tag = self.get_new_metacontacts_tag(brother_jid) + tag = self._get_new_metacontacts_tag(brother_jid) self._metacontacts_tags[brother_account][tag] = [{'jid': brother_jid, 'tag': tag}] if brother_account != account: common.gajim.connections[brother_account].store_metacontacts( self._metacontacts_tags[brother_account]) # be sure jid has no other tag - old_tag = self.get_metacontacts_tag(account, jid) + old_tag = self._get_metacontacts_tag(account, jid) while old_tag: self.remove_metacontact(account, jid) - old_tag = self.get_metacontacts_tag(account, jid) + old_tag = self._get_metacontacts_tag(account, jid) if tag not in self._metacontacts_tags[account]: self._metacontacts_tags[account][tag] = [{'jid': jid, 'tag': tag}] else: @@ -615,6 +607,9 @@ class MetacontactManager(): self._metacontacts_tags[account]) def remove_metacontact(self, account, jid): + if not account in self._metacontacts_tags: + return None + found = None for tag in self._metacontacts_tags[account]: for data in self._metacontacts_tags[account][tag]: @@ -628,7 +623,7 @@ class MetacontactManager(): break def has_brother(self, account, jid, accounts): - tag = self.get_metacontacts_tag(account, jid) + tag = self._get_metacontacts_tag(account, jid) if not tag: return False meta_jids = self.get_metacontacts_jids(tag, accounts) @@ -660,7 +655,7 @@ class MetacontactManager(): '''return the family of the given jid, including jid in the form: [{'account': acct, 'jid': jid, 'order': order}, ] 'order' is optional''' - tag = self.get_metacontacts_tag(account, jid) + tag = self._get_metacontacts_tag(account, jid) return self.get_metacontacts_family_from_tag(account, tag) def get_metacontacts_family_from_tag(self, account, tag): @@ -674,7 +669,7 @@ class MetacontactManager(): answers.append(data) return answers - def compare_metacontacts(self, data1, data2): + def _compare_metacontacts(self, data1, data2): '''compare 2 metacontacts. Data is {'jid': jid, 'account': account, 'order': order} order is optional''' @@ -682,8 +677,8 @@ class MetacontactManager(): jid2 = data2['jid'] account1 = data1['account'] account2 = data2['account'] - contact1 = self.get_contact_with_highest_priority(account1, jid1) - contact2 = self.get_contact_with_highest_priority(account2, jid2) + contact1 = self._contacts.get_contact_with_highest_priority(account1, jid1) + contact2 = self._contacts.get_contact_with_highest_priority(account2, jid2) show_list = ['not in roster', 'error', 'offline', 'invisible', 'dnd', 'xa', 'away', 'chat', 'online', 'requested', 'message'] # contact can be null when a jid listed in the metacontact data @@ -757,7 +752,7 @@ class MetacontactManager(): def get_metacontacts_big_brother(self, family): '''which of the family will be the big brother under wich all others will be ?''' - family.sort(cmp=self.compare_metacontacts) + family.sort(cmp=self._compare_metacontacts) return family[-1] # vim: se ts=3: diff --git a/src/roster_window.py b/src/roster_window.py index 8bb0e0288..07a48b287 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -689,9 +689,7 @@ class RosterWindow: is_observer = contact.is_observer() if is_observer: # if he has a tag, remove it - tag = gajim.contacts.get_metacontacts_tag(account, jid) - if tag: - gajim.contacts.remove_metacontact(account, jid) + gajim.contacts.remove_metacontact(account, jid) # Add contact to roster family = gajim.contacts.get_metacontacts_family(account, jid) From a87693c7350841f97e0ee065ba7c0dd7640edf1a Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Thu, 5 Nov 2009 20:54:32 +0100 Subject: [PATCH 08/19] Remove auto-generated smoketests --- test/unit/test_contacts.py | 41 +------------------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/test/unit/test_contacts.py b/test/unit/test_contacts.py index b1655a0e3..ee9410f85 100644 --- a/test/unit/test_contacts.py +++ b/test/unit/test_contacts.py @@ -5,7 +5,6 @@ import unittest import lib lib.setup_env() -from mock import Mock from common.contacts import CommonContact, Contact, GC_Contact, Contacts from common.xmpp import NS_MUC @@ -33,8 +32,6 @@ class TestCommonContact(unittest.TestCase): self.assertTrue(self.contact.supports(NS_MUC), msg="Must not backtrace on simple check for supported feature") - - class TestContact(TestCommonContact): @@ -67,6 +64,7 @@ class TestGC_Contact(TestCommonContact): for attr in attributes: self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) + class TestContacts(unittest.TestCase): def setUp(self): @@ -100,43 +98,6 @@ class TestContacts(unittest.TestCase): # Not yet implemented to remain backwart compatible # self.assertEqual(contact, copy, msg="Must be equal") - - - # AUto generated tests. Can be dropped after migration - - - def test_creation(self): - contacts = Contacts() -#Makesureitdoesn'traiseanyexceptions. - - def test_add_account_3_times(self): - contacts = Contacts() - - from common import gajim - gajim.connections[u'dingdong.org'] = Mock() - gajim.connections[u'Cool"ch\xe2r\xdf\xe9\xb5\xf6'] = Mock() - gajim.connections[u'acc1'] = Mock() - - self.assertEqual(None, contacts.add_account(account=u'acc1')) - self.assertEqual(None, contacts.add_account(account=u'Cool"ch\xe2r\xdf\xe9\xb5\xf6')) - self.assertEqual(None, contacts.add_account(account=u'dingdong.org')) - - def test_add_metacontact_4_times_and_define_metacontacts_3_times(self): - contacts = Contacts() - - from common import gajim - gajim.connections[u'dingdong.org'] = Mock() - gajim.connections[u'Cool"ch\xe2r\xdf\xe9\xb5\xf6'] = Mock() - gajim.connections[u'acc1'] = Mock() - - self.assertEqual(None, contacts.define_metacontacts(account=u'acc1', tags_list={})) - self.assertEqual(None, contacts.define_metacontacts(account=u'Cool"ch\xe2r\xdf\xe9\xb5\xf6', tags_list={})) - self.assertEqual(None, contacts.define_metacontacts(account=u'dingdong.org', tags_list={})) - self.assertEqual(None, contacts.add_metacontact(account=u'dingdong.org', brother_account=u'dingdong.org', brother_jid=u'guypsych0\\40h.com@msn.dingdong.org', jid=u'guypsych0%h.com@msn.delx.cjb.net', order=None)) - self.assertEqual(None, contacts.add_metacontact(account=u'dingdong.org', brother_account=u'dingdong.org', brother_jid=u'guypsych0\\40h.com@msn.dingdong.org', jid=u'guypsych0%h.com@msn.jabber.wiretrip.org', order=None)) - self.assertEqual(None, contacts.add_metacontact(account=u'dingdong.org', brother_account=u'dingdong.org', brother_jid=u'guypsych0\\40h.com@msn.dingdong.org', jid=u'guypsycho\\40g.com@gtalk.dingdong.org', order=None)) - self.assertEqual(None, contacts.add_metacontact(account=u'Cool"ch\xe2r\xdf\xe9\xb5\xf6', brother_account=u'acc1', brother_jid=u'samejid@gajim.org', jid=u'samejid@gajim.org', order=None)) - if __name__ == "__main__": unittest.main() \ No newline at end of file From 17af7902e45fbbb1e213d84dd665e82efc1f4a1a Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Thu, 5 Nov 2009 22:16:38 +0100 Subject: [PATCH 09/19] Make it explicit whether an ordinary Contact, a Self Contact or a Not-In-Roster Contact is created. --- src/common/contacts.py | 15 +++++++++++++++ src/dialogs.py | 4 +--- src/gui_interface.py | 15 +++++++-------- src/roster_window.py | 17 +++++++++-------- src/search_window.py | 4 +--- src/session.py | 3 ++- 6 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/common/contacts.py b/src/common/contacts.py index a151e216c..c423fe275 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -187,6 +187,7 @@ class Contact(CommonContact): return False + class GC_Contact(CommonContact): '''Information concerning each groupchat contact''' def __init__(self, room_jid, account, name='', show='', status='', role='', @@ -260,6 +261,20 @@ class Contacts: keyID=keyID, client_caps=client_caps, our_chatstate=our_chatstate, chatstate=chatstate, last_status_time=last_status_time, composing_xep=composing_xep, mood=mood, tune=tune, activity=activity) + + def create_self_contact(self, jid, account, resource, show, status, priority, keyID=''): + conn = common.gajim.connections[account] + nick = common.gajim.nicks[account] + return self.create_contact(jid=jid, account=account, + name=nick, groups=['self_contact'], show=show, status=status, + sub='both', ask='none', priority=priority, keyID=keyID, + resource=resource, mood=conn.mood, tune=conn.tune, + activity=conn.activity) + + def create_not_in_roster_contact(self, jid, account, resource='', name='', keyID=''): + return self.create_contact(jid=jid, account=account, resource=resource, + name=name, groups=[_('Not in Roster')], show='not in roster', + status='', sub='none', keyID=keyID) def copy_contact(self, contact): return self.create_contact(jid=contact.jid, account=contact.account, diff --git a/src/dialogs.py b/src/dialogs.py index d9cf91328..8be8e9201 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -1888,9 +1888,7 @@ class SubscriptionRequestWindow: if self.jid in gajim.interface.instances[self.account]['infos']: gajim.interface.instances[self.account]['infos'][self.jid].window.present() else: - contact = gajim.contacts.create_contact(jid=self.jid, account=self.account, name='', - groups=[], show='', status='', sub='', ask='', resource='', - priority=5, keyID='', our_chatstate=None, chatstate=None) + contact = gajim.contacts.create_contact(jid=self.jid, account=self.account) gajim.interface.instances[self.account]['infos'][self.jid] = \ vcard.VcardWindow(contact, self.account) # Remove jabber page diff --git a/src/gui_interface.py b/src/gui_interface.py index 179a9c02b..d65625ffa 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -344,11 +344,9 @@ class Interface: # Ignore offline presence of unknown self resource if new_show < 2: return - contact1 = gajim.contacts.create_contact(jid=ji, account=account, - name=gajim.nicks[account], groups=['self_contact'], - show=array[1], status=status_message, sub='both', ask='none', - priority=priority, keyID=keyID, resource=resource, - mood=conn.mood, tune=conn.tune, activity=conn.activity) + contact1 = gajim.contacts.create_self_contact(jid=ji, + account=account, show=array[1], status=status_message, + priority=priority, keyID=keyID, resource=resource) old_show = 0 gajim.contacts.add_contact(account, contact1) lcontact.append(contact1) @@ -1353,9 +1351,8 @@ class Interface: 'attached_gpg_keys').split() if jid in attached_keys: keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_contact(jid=jid, account=account, name='', - groups=[_('Not in Roster')], show='not in roster', status='', - sub='none', keyID=keyID) + contact = gajim.contacts.create_not_in_roster_contact(jid=jid, + account=account, keyID=keyID) gajim.contacts.add_contact(account, contact) self.roster.add_contact(contact.jid, account) file_props = array[1] @@ -2567,6 +2564,7 @@ class Interface: account): # Join new groupchat if minimize: + #GCMIN contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick) gc_control = GroupchatControl(None, contact, account) gajim.interface.minimized_controls[account][room_jid] = gc_control @@ -2590,6 +2588,7 @@ class Interface: def new_room(self, room_jid, nick, account, is_continued=False): # Get target window, create a control, and associate it with the window + # GCMIN contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick) mw = self.msg_win_mgr.get_window(contact.jid, account) if not mw: diff --git a/src/roster_window.py b/src/roster_window.py index 07a48b287..8c85a70d1 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -806,6 +806,7 @@ class RosterWindow: else: name = jid.split('@')[0] # New groupchat + #GCMIN contact = gajim.contacts.create_contact(jid=jid, account=account, name=name, groups=[_('Groupchats')], show=show, status=status, sub='none') gajim.contacts.add_contact(account, contact) @@ -841,6 +842,7 @@ class RosterWindow: Return the added contact instance.''' contact = gajim.contacts.get_contact_with_highest_priority(account, jid) if contact is None: + #TRANSP contact = gajim.contacts.create_contact(jid=jid, account=account, name=jid, groups=[_('Transports')], show='offline', status='offline', sub='from') @@ -982,9 +984,8 @@ class RosterWindow: 'attached_gpg_keys').split() if jid in attached_keys: keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_contact(jid=jid, account=account, name=nick, - groups=[_('Not in Roster')], show='not in roster', status='', - sub='none', resource=resource, keyID=keyID) + contact = gajim.contacts.create_not_in_roster_contact(jid=jid, + account=account, resource=resource, name=nick, keyID=keyID) gajim.contacts.add_contact(account, contact) self.add_contact(contact.jid, account) return contact @@ -1772,6 +1773,7 @@ class RosterWindow: if gajim.jid_is_transport(jid): array[jid]['groups'] = [_('Transports')] + #TRANSP - potential contact1 = gajim.contacts.create_contact(jid=ji, account=account, name=name, groups=array[jid]['groups'], show=show, status=status, sub=array[jid]['subscription'], ask=array[jid]['ask'], @@ -2538,11 +2540,10 @@ class RosterWindow: show = roster.getShow(jid+'/'+resource) if not show: show = 'online' - contact = gajim.contacts.create_contact(jid=jid, account=account, - name=account, groups=['self_contact'], show=show, - status=roster.getStatus(jid + '/' + resource), - resource=resource, - priority=roster.getPriority(jid + '/' + resource)) + contact = gajim.contacts.create_self_contact(jid=jid, + account=account, show=show, status=roster.getStatus(jid + '/' + resource), + priority=roster.getPriority(jid + '/' + resource), + resource=resource) contacts.append(contact) if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: self.tooltip.id = row diff --git a/src/search_window.py b/src/search_window.py index e383e7c4f..3a21db543 100644 --- a/src/search_window.py +++ b/src/search_window.py @@ -110,9 +110,7 @@ class SearchWindow: if jid in gajim.interface.instances[self.account]['infos']: gajim.interface.instances[self.account]['infos'][jid].window.present() else: - contact = gajim.contacts.create_contact(jid=jid, account=self.account, name='', groups=[], - show='', status='', sub='', ask='', resource='', priority=0, - keyID='', our_chatstate=None, chatstate=None) + contact = gajim.contacts.create_contact(jid=jid, account=self.account) gajim.interface.instances[self.account]['infos'][jid] = \ vcard.VcardWindow(contact, self.account) diff --git a/src/session.py b/src/session.py index 6438c518e..52f7b6045 100644 --- a/src/session.py +++ b/src/session.py @@ -203,7 +203,8 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): if not contact: # contact is not in the roster, create a fake one to display # notification - contact = gajim.contacts.create_contact(jid=jid, account=self.conn.name, resource=resource) + contact = gajim.contacts.create_not_in_roster_contact(jid=jid, + account=self.conn.name, resource=resource) advanced_notif_num = notify.get_advanced_notification('message_received', self.conn.name, contact) From cec93b61357659ec933e75902617328a65114378 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Mon, 9 Nov 2009 21:26:56 +0100 Subject: [PATCH 10/19] Push method to check if a specific ClientCaps supports a feature down to the caps module. Public interfaces stay the same. --- src/common/caps.py | 27 ++++++++++++++++++++++++++- src/common/contacts.py | 20 +++----------------- test/unit/test_caps.py | 16 +++++++--------- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/common/caps.py b/src/common/caps.py index b3fe72e9c..bb52bdcf4 100644 --- a/src/common/caps.py +++ b/src/common/caps.py @@ -41,12 +41,29 @@ from common.xmpp import NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION] +################################################################################ +### Public API of this module +################################################################################ + capscache = None def initialize(logger): - ''' Initializes the capscache global ''' + ''' Initializes this module ''' global capscache capscache = CapsCache(logger) +def client_supports(client_caps, requested_feature): + lookup_item = client_caps.get_cache_lookup_strategy() + cache_item = lookup_item(capscache) + + supported_features = cache_item.features + if requested_feature in supported_features: + return True + elif supported_features == [] and cache_item.queried in (0, 1): + # assume feature is supported, if we don't know yet, what the client + # is capable of + return requested_feature not in FEATURE_BLACKLIST + else: + return False def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'): '''Compute caps hash according to XEP-0115, V1.5 @@ -118,6 +135,10 @@ def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'): return base64.b64encode(hash_.digest()) +################################################################################ +### Internal classes of this module +################################################################################ + class AbstractClientCaps(object): ''' Base class representing a client and its capabilities as advertised by @@ -323,6 +344,10 @@ class CapsCache(object): discover(connection, jid) +################################################################################ +### Caps network coding +################################################################################ + class ConnectionCaps(object): ''' This class highly depends on that it is a part of Connection class. diff --git a/src/common/contacts.py b/src/common/contacts.py index c423fe275..34f45c914 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -29,7 +29,7 @@ ## import common.gajim -from common import caps +import caps class XMPPEntity(object): '''Base representation of entities in XMPP''' @@ -72,7 +72,7 @@ class CommonContact(XMPPEntity): def get_shown_name(self): raise NotImplementedError - + def supports(self, requested_feature): ''' Returns True if the contact has advertised to support the feature @@ -85,21 +85,7 @@ class CommonContact(XMPPEntity): # return caps for a contact that has no resources left. return False else: - return self._client_supports(requested_feature) - - def _client_supports(self, requested_feature): - lookup_item = self.client_caps.get_cache_lookup_strategy() - cache_item = lookup_item(caps.capscache) - - supported_features = cache_item.features - if requested_feature in supported_features: - return True - elif supported_features == [] and cache_item.queried in (0, 1): - # assume feature is supported, if we don't know yet, what the client - # is capable of - return requested_feature not in caps.FEATURE_BLACKLIST - else: - return False + return caps.client_supports(self.client_caps, requested_feature) class Contact(CommonContact): diff --git a/test/unit/test_caps.py b/test/unit/test_caps.py index 9ac466c47..160996b6a 100644 --- a/test/unit/test_caps.py +++ b/test/unit/test_caps.py @@ -113,25 +113,23 @@ class TestClientCaps(CommonCapsTest): "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=") def test_client_supports(self): - contact = Contact(jid=None, account=None, client_caps=self.client_caps) - - self.assertTrue(contact.supports(NS_PING), + self.assertTrue(caps.client_supports(self.client_caps, NS_PING), msg="Assume supported, if we don't have caps") - self.assertFalse(contact.supports(NS_XHTML_IM), + self.assertFalse(caps.client_supports(self.client_caps, NS_XHTML_IM), msg="Must not assume blacklisted feature is supported on default") self.cc.initialize_from_db() - self.assertFalse(contact.supports(NS_PING), + self.assertFalse(caps.client_supports(self.client_caps, NS_PING), msg="Must return false on unsupported feature") - self.assertTrue(contact.supports(NS_XHTML_IM), + self.assertTrue(caps.client_supports(self.client_caps, NS_XHTML_IM), msg="Must return True on supported feature") - self.assertTrue(contact.supports(NS_MUC), - msg="Must return True on supported feature") - + self.assertTrue(caps.client_supports(self.client_caps, NS_MUC), + msg="Must return True on supported feature") + class TestOldClientCaps(TestClientCaps): From d5ac527f84ed2d9aa76ada923dfc11e23e9f5de8 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Mon, 9 Nov 2009 21:51:43 +0100 Subject: [PATCH 11/19] Filter duplicate groups centrally. --- src/common/contacts.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/common/contacts.py b/src/common/contacts.py index 34f45c914..7f6f0dc0c 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -99,7 +99,7 @@ class Contact(CommonContact): our_chatstate, composing_xep, chatstate, client_caps=client_caps) self.contact_name = '' # nick choosen by contact - self.groups = groups + self.groups = [i for i in set(groups)] # filter duplicate values self.sub = sub self.ask = ask @@ -236,13 +236,7 @@ class Contacts: our_chatstate=None, chatstate=None, last_status_time=None, composing_xep=None, mood={}, tune={}, activity={}): - # We don't want duplicated group values - groups_unique = [] - for group in groups: - if group not in groups_unique: - groups_unique.append(group) - - return Contact(jid=jid, account=account, name=name, groups=groups_unique, + return Contact(jid=jid, account=account, name=name, groups=groups, show=show, status=status, sub=sub, ask=ask, resource=resource, priority=priority, keyID=keyID, client_caps=client_caps, our_chatstate=our_chatstate, chatstate=chatstate, last_status_time=last_status_time, From 004619f99e3e41762febc1503982d70886e84fa8 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Mon, 9 Nov 2009 22:03:14 +0100 Subject: [PATCH 12/19] Work around a reported NoneType exception. --- src/common/connection_handlers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index b3f95691c..e134aa003 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -2468,8 +2468,12 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, for sess in self.sessions[jid].values(): if not sess.received_thread_id: contact = gajim.contacts.get_contact(self.name, jid) - - session_supported = contact.supports(common.xmpp.NS_SSN) or \ + # FIXME: I don't know if this is the correct behavior here. + # Anyway, it is the old behavior when we assumed that + # not-existing contacts don't support anything + contact_exists = bool(contact) + session_supported = contact_exists and \ + contact.supports(common.xmpp.NS_SSN) or \ contact.supports(common.xmpp.NS_ESESSION) if session_supported: sess.terminate() From ba9ef8301b3da9d751a65bc9f56207922719b192 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Mon, 9 Nov 2009 23:03:16 +0100 Subject: [PATCH 13/19] Remove superfluous clear_contacts method --- src/common/contacts.py | 3 --- test/integration/test_gui_event_integration.py | 11 ++++------- test/integration/test_roster.py | 18 +++++++----------- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/common/contacts.py b/src/common/contacts.py index 7f6f0dc0c..bcb3331ba 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -298,9 +298,6 @@ class Contacts: if len(self._contacts[account][contact.jid]) == 0: del self._contacts[account][contact.jid] - def clear_contacts(self, account): - self._contacts[account] = {} - def remove_jid(self, account, jid, remove_meta=True): '''Removes all contacts for a given jid''' if account not in self._contacts: diff --git a/test/integration/test_gui_event_integration.py b/test/integration/test_gui_event_integration.py index 00ed5c696..8759e2259 100644 --- a/test/integration/test_gui_event_integration.py +++ b/test/integration/test_gui_event_integration.py @@ -7,6 +7,7 @@ import lib lib.setup_env() from common import gajim +from common import contacts as contacts_module from gajim import Interface from gajim_mocks import * @@ -25,6 +26,9 @@ class TestStatusChange(unittest.TestCase): '''tests gajim.py's incredibly complex handle_event_notify''' def setUp(self): + + gajim.connections = {} + gajim.contacts = contacts_module.Contacts() gajim.interface.roster = roster_window.RosterWindow() for acc in contacts: @@ -38,13 +42,6 @@ class TestStatusChange(unittest.TestCase): self.assertEqual(0, len(notify.notifications)) def tearDown(self): - gajim.interface.roster.model.clear() - - for acc in contacts: - gajim.contacts.clear_contacts(acc) - - del gajim.interface.roster - notify.notifications = [] def contact_comes_online(self, account, jid, resource, prio): diff --git a/test/integration/test_roster.py b/test/integration/test_roster.py index d093d0e64..acb91b71c 100644 --- a/test/integration/test_roster.py +++ b/test/integration/test_roster.py @@ -9,15 +9,16 @@ from mock import Mock, expectParams from gajim_mocks import * from common import gajim +from common import contacts as contacts_module import roster_window gajim.get_jid_from_account = lambda acc: 'myjid@' + acc + class TestRosterWindow(unittest.TestCase): def setUp(self): gajim.interface = MockInterface() - self.roster = roster_window.RosterWindow() self.C_NAME = roster_window.C_NAME self.C_TYPE = roster_window.C_TYPE @@ -26,13 +27,13 @@ class TestRosterWindow(unittest.TestCase): # Add after creating RosterWindow # We want to test the filling explicitly + gajim.contacts = contacts_module.Contacts() + gajim.connections = {} + self.roster = roster_window.RosterWindow() + for acc in contacts: gajim.connections[acc] = MockConnection(acc) - - def tearDown(self): - self.roster.model.clear() - for acc in gajim.contacts.get_accounts(): - gajim.contacts.clear_contacts(acc) + gajim.contacts.add_account(acc) ### Custom assertions def assert_all_contacts_are_in_roster(self, acc): @@ -142,11 +143,6 @@ class TestRosterWindow(unittest.TestCase): groups = contacts[acc][jid]['groups'] or ['General',] - # cleanup - self.roster.model.clear() - for acc in contacts: - gajim.contacts.clear_contacts(acc) - def test_fill_roster_model(self): for acc in contacts: self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc) From 2c296f3fe0197d5f146569c917663c3069e02f06 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Tue, 10 Nov 2009 19:05:47 +0100 Subject: [PATCH 14/19] Create a GC_Contacts() class and move groupchat related behavior to it. This is similar to what has been done with the MetacontactManager --- src/common/contacts.py | 53 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/src/common/contacts.py b/src/common/contacts.py index bcb3331ba..2d90b6a66 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -204,22 +204,21 @@ class Contacts: '''Information concerning all contacts and groupchat contacts''' def __init__(self): self._contacts = {} # list of contacts {acct: {jid1: [C1, C2]}, } one Contact per resource - self._gc_contacts = {} # list of contacts that are in gc {acct: {room_jid: {nick: C}}} - self._metacontact_manager = MetacontactManager(self); + self._gc_contacts = GC_Contacts() + self._metacontact_manager = MetacontactManager(self) def change_account_name(self, old_name, new_name): self._contacts[new_name] = self._contacts[old_name] - self._gc_contacts[new_name] = self._gc_contacts[old_name] del self._contacts[old_name] - del self._gc_contacts[old_name] + self._gc_contacts.change_account_name(old_name, new_name) self._metacontact_manager.change_account_name(old_name, new_name) def add_account(self, account): self._contacts[account] = {} - self._gc_contacts[account] = {} + self._gc_contacts.add_account(account) self._metacontact_manager.add_account(account) def get_accounts(self): @@ -227,8 +226,8 @@ class Contacts: def remove_account(self, account): del self._contacts[account] - del self._gc_contacts[account] + self._gc_contacts.remove_account(account) self._metacontact_manager.remove_account(account) def create_contact(self, jid, account, name='', groups=[], show='', status='', @@ -457,6 +456,48 @@ class Contacts: def get_jid_list(self, account): return self._contacts[account].keys() + def create_gc_contact(self, room_jid, account, name='', show='', status='', + role='', affiliation='', jid='', resource=''): + return self._gc_contacts.create_gc_contact(room_jid, account, name, show, status, role, affiliation, jid, resource) + + def add_gc_contact(self, account, gc_contact): + return self._gc_contacts.add_gc_contact(account, gc_contact) + + def remove_gc_contact(self, account, gc_contact): + return self._gc_contacts.remove_gc_contact(account, gc_contact) + + def remove_room(self, account, room_jid): + return self._gc_contacts.remove_room(account, room_jid) + + def get_gc_list(self, account): + return self._gc_contacts.get_gc_list(account) + + def get_nick_list(self, account, room_jid): + return self._gc_contacts.get_nick_list(account, room_jid) + + def get_gc_contact(self, account, room_jid, nick): + return self._gc_contacts.get_gc_contact(account, room_jid, nick) + + def get_nb_role_total_gc_contacts(self, account, room_jid, role): + return self._gc_contacts.get_nb_role_total_gc_contacts(account, room_jid, role) + + +class GC_Contacts(): + + def __init__(self): + self._gc_contacts = {} # list of contacts that are in gc {acct: {room_jid: {nick: C}}} + + def change_account_name(self, old_name, new_name): + self._gc_contacts[new_name] = self._gc_contacts[old_name] + del self._gc_contacts[old_name] + + def add_account(self, account): + if account not in self._gc_contacts: + self._gc_contacts[account] = {} + + def remove_account(self, account): + del self._gc_contacts[account] + def create_gc_contact(self, room_jid, account, name='', show='', status='', role='', affiliation='', jid='', resource=''): return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid, From 3ddc5ce78eb2e13fc1fa934e1b2670577802bb81 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Tue, 10 Nov 2009 19:26:17 +0100 Subject: [PATCH 15/19] Use implicit delegation with __getattr__ instead of explicit delegation via method calls --- src/common/contacts.py | 68 ++++++++---------------------------------- 1 file changed, 12 insertions(+), 56 deletions(-) diff --git a/src/common/contacts.py b/src/common/contacts.py index 2d90b6a66..a5c70cd55 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -411,36 +411,6 @@ class Contacts: nbr_total += 1 return nbr_online, nbr_total - def define_metacontacts(self, account, tags_list): - return self._metacontact_manager.define_metacontacts(account, tags_list) - - def get_metacontacts_tags(self, account): - return self._metacontact_manager.get_metacontacts_tags(account) - - def add_metacontact(self, brother_account, brother_jid, account, jid, order=None): - return self._metacontact_manager.add_metacontact(brother_account, brother_jid, account, jid, order) - - def remove_metacontact(self, account, jid): - return self._metacontact_manager.remove_metacontact(account, jid) - - def has_brother(self, account, jid, accounts): - return self._metacontact_manager.has_brother(account, jid, accounts) - - def is_big_brother(self, account, jid, accounts): - return self._metacontact_manager.is_big_brother(account, jid, accounts) - - def get_metacontacts_jids(self, tag, accounts): - return self._metacontact_manager.get_metacontacts_jids(tag, accounts) - - def get_metacontacts_family(self, account, jid): - return self._metacontact_manager.get_metacontacts_family(account, jid) - - def get_metacontacts_family_from_tag(self, account, tag): - return self._metacontact_manager.get_metacontacts_family_from_tag(account, tag) - - def get_metacontacts_big_brother(self, family): - return self._metacontact_manager.get_metacontacts_big_brother(family) - def is_pm_from_jid(self, account, jid): '''Returns True if the given jid is a private message jid''' if jid in self._contacts[account]: @@ -452,34 +422,20 @@ class Contacts: if isinstance(contact, Contact): return False return True - + def get_jid_list(self, account): return self._contacts[account].keys() - def create_gc_contact(self, room_jid, account, name='', show='', status='', - role='', affiliation='', jid='', resource=''): - return self._gc_contacts.create_gc_contact(room_jid, account, name, show, status, role, affiliation, jid, resource) - - def add_gc_contact(self, account, gc_contact): - return self._gc_contacts.add_gc_contact(account, gc_contact) - - def remove_gc_contact(self, account, gc_contact): - return self._gc_contacts.remove_gc_contact(account, gc_contact) - - def remove_room(self, account, room_jid): - return self._gc_contacts.remove_room(account, room_jid) - - def get_gc_list(self, account): - return self._gc_contacts.get_gc_list(account) - - def get_nick_list(self, account, room_jid): - return self._gc_contacts.get_nick_list(account, room_jid) - - def get_gc_contact(self, account, room_jid, nick): - return self._gc_contacts.get_gc_contact(account, room_jid, nick) - - def get_nb_role_total_gc_contacts(self, account, room_jid, role): - return self._gc_contacts.get_nb_role_total_gc_contacts(account, room_jid, role) + + def __getattr__(self, attr_name): + # Only called if self has no attr_name + if hasattr(self._gc_contacts, attr_name): + return getattr(self._gc_contacts, attr_name) + elif hasattr(self._metacontact_manager, attr_name): + return getattr(self._metacontact_manager, attr_name) + else: + raise AttributeError(attr_name) + class GC_Contacts(): @@ -508,7 +464,7 @@ class GC_Contacts(): # No such account before ? if account not in self._gc_contacts: - self._contacts[account] = {gc_contact.room_jid : {gc_contact.name: \ + self._gc_contacts[account] = {gc_contact.room_jid : {gc_contact.name: \ gc_contact}} return # No such room_jid before ? From b4285302db1fb8583b19de3e9489c320ae766f4a Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Tue, 10 Nov 2009 21:08:25 +0100 Subject: [PATCH 16/19] Make the GC_Contacts class 'account insensitive'. Instead, create an Account class which holds a GC_Contacts object. The API has been preserved. For now the old Contacts() API has not been changed. --- src/common/account.py | 26 +++++++ src/common/contacts.py | 138 +++++++++++++++++--------------------- test/runtests.py | 1 + test/unit/test_account.py | 19 ++++++ 4 files changed, 109 insertions(+), 75 deletions(-) create mode 100644 src/common/account.py create mode 100644 test/unit/test_account.py diff --git a/src/common/account.py b/src/common/account.py new file mode 100644 index 000000000..4dbfb9e77 --- /dev/null +++ b/src/common/account.py @@ -0,0 +1,26 @@ +# -*- coding:utf-8 -*- +## src/common/contacts.py +## +## Copyright (C) 2009 Stephan Erb +## +## This file is part of Gajim. +## +## Gajim 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; version 3 only. +## +## Gajim 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 Gajim. If not, see . +## + +class Account(object): + + def __init__(self, gc_contacts): + self.gc_contacts = gc_contacts + + \ No newline at end of file diff --git a/src/common/contacts.py b/src/common/contacts.py index a5c70cd55..81be70e8e 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -30,6 +30,7 @@ import common.gajim import caps +from account import Account class XMPPEntity(object): '''Base representation of entities in XMPP''' @@ -204,21 +205,21 @@ class Contacts: '''Information concerning all contacts and groupchat contacts''' def __init__(self): self._contacts = {} # list of contacts {acct: {jid1: [C1, C2]}, } one Contact per resource - - self._gc_contacts = GC_Contacts() self._metacontact_manager = MetacontactManager(self) + self._accounts = {} def change_account_name(self, old_name, new_name): self._contacts[new_name] = self._contacts[old_name] del self._contacts[old_name] - self._gc_contacts.change_account_name(old_name, new_name) + self._accounts[new_name] = self._accounts[old_name] + del self._accounts[old_name] + self._metacontact_manager.change_account_name(old_name, new_name) def add_account(self, account): self._contacts[account] = {} - - self._gc_contacts.add_account(account) + self._accounts[account] = Account(GC_Contacts()) self._metacontact_manager.add_account(account) def get_accounts(self): @@ -226,8 +227,7 @@ class Contacts: def remove_account(self, account): del self._contacts[account] - - self._gc_contacts.remove_account(account) + del self._accounts[account] self._metacontact_manager.remove_account(account) def create_contact(self, jid, account, name='', groups=[], show='', status='', @@ -426,33 +426,12 @@ class Contacts: def get_jid_list(self, account): return self._contacts[account].keys() - def __getattr__(self, attr_name): # Only called if self has no attr_name - if hasattr(self._gc_contacts, attr_name): - return getattr(self._gc_contacts, attr_name) - elif hasattr(self._metacontact_manager, attr_name): + if hasattr(self._metacontact_manager, attr_name): return getattr(self._metacontact_manager, attr_name) else: raise AttributeError(attr_name) - - - -class GC_Contacts(): - - def __init__(self): - self._gc_contacts = {} # list of contacts that are in gc {acct: {room_jid: {nick: C}}} - - def change_account_name(self, old_name, new_name): - self._gc_contacts[new_name] = self._gc_contacts[old_name] - del self._gc_contacts[old_name] - - def add_account(self, account): - if account not in self._gc_contacts: - self._gc_contacts[account] = {} - - def remove_account(self, account): - del self._gc_contacts[account] def create_gc_contact(self, room_jid, account, name='', show='', status='', role='', affiliation='', jid='', resource=''): @@ -460,68 +439,77 @@ class GC_Contacts(): resource) def add_gc_contact(self, account, gc_contact): - assert account == gc_contact.account # migration check - - # No such account before ? - if account not in self._gc_contacts: - self._gc_contacts[account] = {gc_contact.room_jid : {gc_contact.name: \ - gc_contact}} - return - # No such room_jid before ? - if gc_contact.room_jid not in self._gc_contacts[account]: - self._gc_contacts[account][gc_contact.room_jid] = {gc_contact.name: \ - gc_contact} - return - self._gc_contacts[account][gc_contact.room_jid][gc_contact.name] = \ - gc_contact + return self._accounts[account].gc_contacts.add_gc_contact(gc_contact) def remove_gc_contact(self, account, gc_contact): - if account not in self._gc_contacts: - return - if gc_contact.room_jid not in self._gc_contacts[account]: - return - if gc_contact.name not in self._gc_contacts[account][ - gc_contact.room_jid]: - return - del self._gc_contacts[account][gc_contact.room_jid][gc_contact.name] - # It was the last nick in room ? - if not len(self._gc_contacts[account][gc_contact.room_jid]): - del self._gc_contacts[account][gc_contact.room_jid] + return self._accounts[account].gc_contacts.remove_gc_contact(gc_contact) def remove_room(self, account, room_jid): - if account not in self._gc_contacts: - return - if room_jid not in self._gc_contacts[account]: - return - del self._gc_contacts[account][room_jid] + return self._accounts[account].gc_contacts.remove_room(room_jid) def get_gc_list(self, account): - if account not in self._gc_contacts: - return [] - return self._gc_contacts[account].keys() + return self._accounts[account].gc_contacts.get_gc_list() def get_nick_list(self, account, room_jid): - gc_list = self.get_gc_list(account) - if not room_jid in gc_list: - return [] - return self._gc_contacts[account][room_jid].keys() + return self._accounts[account].gc_contacts.get_nick_list(room_jid) def get_gc_contact(self, account, room_jid, nick): - nick_list = self.get_nick_list(account, room_jid) - if not nick in nick_list: - return None - return self._gc_contacts[account][room_jid][nick] + return self._accounts[account].gc_contacts.get_gc_contact(room_jid, nick) def get_nb_role_total_gc_contacts(self, account, room_jid, role): + return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role) + + +class GC_Contacts(): + + def __init__(self): + # list of contacts that are in gc {room_jid: {nick: C}}} + self._rooms = {} + + def add_gc_contact(self, gc_contact): + if gc_contact.room_jid not in self._rooms: + self._rooms[gc_contact.room_jid] = {gc_contact.name: gc_contact} + else: + self._rooms[gc_contact.room_jid][gc_contact.name] = gc_contact + + def remove_gc_contact(self, gc_contact): + if gc_contact.room_jid not in self._rooms: + return + if gc_contact.name not in self._rooms[gc_contact.room_jid]: + return + del self._rooms[gc_contact.room_jid][gc_contact.name] + # It was the last nick in room ? + if not len(self._rooms[gc_contact.room_jid]): + del self._rooms[gc_contact.room_jid] + + def remove_room(self, room_jid): + if room_jid not in self._rooms: + return + del self._rooms[room_jid] + + def get_gc_list(self): + return self._rooms.keys() + + def get_nick_list(self, room_jid): + gc_list = self.get_gc_list() + if not room_jid in gc_list: + return [] + return self._rooms[room_jid].keys() + + def get_gc_contact(self, room_jid, nick): + nick_list = self.get_nick_list(room_jid) + if not nick in nick_list: + return None + return self._rooms[room_jid][nick] + + def get_nb_role_total_gc_contacts(self, room_jid, role): '''Returns the number of group chat contacts for the given role and the total number of group chat contacts''' - if account not in self._gc_contacts: - return 0, 0 - if room_jid not in self._gc_contacts[account]: + if room_jid not in self._rooms: return 0, 0 nb_role = nb_total = 0 - for nick in self._gc_contacts[account][room_jid]: - if self._gc_contacts[account][room_jid][nick].role == role: + for nick in self._rooms[room_jid]: + if self._rooms[room_jid][nick].role == role: nb_role += 1 nb_total += 1 return nb_role, nb_total diff --git a/test/runtests.py b/test/runtests.py index 5a9f5f663..4f8ebea30 100755 --- a/test/runtests.py +++ b/test/runtests.py @@ -41,6 +41,7 @@ modules = ( 'unit.test_xmpp_dispatcher_nb', 'unit.test_contacts', 'unit.test_gui_interface', 'unit.test_sessions', + 'unit.test_account', ) #modules = () diff --git a/test/unit/test_account.py b/test/unit/test_account.py new file mode 100644 index 000000000..faa577b7f --- /dev/null +++ b/test/unit/test_account.py @@ -0,0 +1,19 @@ +''' +Tests for Account classes +''' +import unittest + +import lib +lib.setup_env() + +from common.account import Account + +class Test(unittest.TestCase): + + def testInstantiate(self): + account = Account(gc_contacts=None) + + self.assertTrue(account.gc_contacts is None) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From ae9376ff6368bc30140315bcfccf73b186bb21d4 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Tue, 10 Nov 2009 22:56:10 +0100 Subject: [PATCH 17/19] Split the 'account sensitive' parts of the Contacts class and move them to an intermediate Contacts_New class. The Contact class remains the public interface for contact handling. This is only a single step of a longer refactoring to empower the Account class. --- src/common/account.py | 4 +- src/common/contacts.py | 212 +++++++++++++++++++++---------------- test/unit/test_account.py | 4 +- test/unit/test_contacts.py | 28 ++++- 4 files changed, 152 insertions(+), 96 deletions(-) diff --git a/src/common/account.py b/src/common/account.py index 4dbfb9e77..387559199 100644 --- a/src/common/account.py +++ b/src/common/account.py @@ -20,7 +20,9 @@ class Account(object): - def __init__(self, gc_contacts): + def __init__(self, name, contacts, gc_contacts): + self.name = name + self.contacts = contacts self.gc_contacts = gc_contacts \ No newline at end of file diff --git a/src/common/contacts.py b/src/common/contacts.py index 81be70e8e..b766e3d64 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -174,7 +174,6 @@ class Contact(CommonContact): return False - class GC_Contact(CommonContact): '''Information concerning each groupchat contact''' def __init__(self, room_jid, account, name='', show='', status='', role='', @@ -204,29 +203,25 @@ class GC_Contact(CommonContact): class Contacts: '''Information concerning all contacts and groupchat contacts''' def __init__(self): - self._contacts = {} # list of contacts {acct: {jid1: [C1, C2]}, } one Contact per resource + self._metacontact_manager = MetacontactManager(self) self._accounts = {} - def change_account_name(self, old_name, new_name): - self._contacts[new_name] = self._contacts[old_name] - del self._contacts[old_name] - + def change_account_name(self, old_name, new_name): self._accounts[new_name] = self._accounts[old_name] del self._accounts[old_name] self._metacontact_manager.change_account_name(old_name, new_name) - def add_account(self, account): - self._contacts[account] = {} - self._accounts[account] = Account(GC_Contacts()) - self._metacontact_manager.add_account(account) + def add_account(self, account_name): + self._accounts[account_name] = Account(account_name, + Contacts_New(), GC_Contacts()) + self._metacontact_manager.add_account(account_name) def get_accounts(self): - return self._contacts.keys() + return self._accounts.keys() def remove_account(self, account): - del self._contacts[account] del self._accounts[account] self._metacontact_manager.remove_account(account) @@ -264,82 +259,41 @@ class Contacts: chatstate=contact.chatstate, last_status_time=contact.last_status_time) def add_contact(self, account, contact): - assert account == contact.account # migration check - - # No such account before ? - if account not in self._contacts: - self._contacts[account] = {contact.jid : [contact]} - return - # No such jid before ? - if contact.jid not in self._contacts[account]: - self._contacts[account][contact.jid] = [contact] - return - contacts = self._contacts[account][contact.jid] - # We had only one that was offline, remove it - if len(contacts) == 1 and contacts[0].show == 'offline': - # Do not use self.remove_contact: it deteles - # self._contacts[account][contact.jid] - contacts.remove(contacts[0]) - # If same JID with same resource already exists, use the new one - for c in contacts: - if c.resource == contact.resource: - self.remove_contact(account, c) - break - contacts.append(contact) + if account not in self._accounts: + self.add_account(account) + return self._accounts[account].contacts.add_contact(contact) def remove_contact(self, account, contact): - if account not in self._contacts: + if account not in self._accounts: return - if contact.jid not in self._contacts[account]: - return - if contact in self._contacts[account][contact.jid]: - self._contacts[account][contact.jid].remove(contact) - if len(self._contacts[account][contact.jid]) == 0: - del self._contacts[account][contact.jid] + return self._accounts[account].contacts.remove_contact(contact) def remove_jid(self, account, jid, remove_meta=True): - '''Removes all contacts for a given jid''' - if account not in self._contacts: - return - if jid not in self._contacts[account]: - return - del self._contacts[account][jid] + self._accounts[account].contacts.remove_jid(jid) if remove_meta: self._metacontact_manager.remove_metacontact(account, jid) - + def get_contacts(self, account, jid): - '''Returns the list of contact instances for this jid.''' - if jid in self._contacts[account]: - return self._contacts[account][jid] - else: - return [] + return self._accounts[account].contacts.get_contacts(jid) def get_contact(self, account, jid, resource=None): - ### WARNING ### - # This function returns a *RANDOM* resource if resource = None! - # Do *NOT* use if you need to get the contact to which you - # send a message for example, as a bare JID in Jabber means - # highest available resource, which this function ignores! - '''Returns the contact instance for the given resource if it's given else - the first contact is no resource is given or None if there is not''' - if jid in self._contacts[account]: - if not resource: - return self._contacts[account][jid][0] - for c in self._contacts[account][jid]: - if c.resource == resource: - return c - return None + return self._accounts[account].contacts.get_contact(jid, resource=resource) def iter_contacts(self, account): - if account in self._contacts: - for jid in self._contacts[account].keys(): - for contact in self._contacts[account][jid][:]: - yield contact + for contact in self._accounts[account].contacts.iter_contacts(): + yield contact def get_contact_from_full_jid(self, account, fjid): - ''' Get Contact object for specific resource of given jid''' - barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid) - return self.get_contact(account, barejid, resource) + return self._accounts[account].contacts.get_contact_from_full_jid(fjid) + + def get_first_contact_from_jid(self, account, jid): + return self._accounts[account].contacts.get_first_contact_from_jid(jid) + + def get_contacts_from_group(self, account, group): + return self._accounts[account].contacts.get_contacts_from_group(group) + + def get_jid_list(self, account): + return self._accounts[account].contacts.get_jid_list() def get_highest_prio_contact_from_contacts(self, contacts): if not contacts: @@ -358,21 +312,7 @@ class Contacts: contact = self.get_gc_contact(account, room, nick) return contact return self.get_highest_prio_contact_from_contacts(contacts) - - def get_first_contact_from_jid(self, account, jid): - if jid in self._contacts[account]: - return self._contacts[account][jid][0] - return None - - def get_contacts_from_group(self, account, group): - '''Returns all contacts in the given group''' - group_contacts = [] - for jid in self._contacts[account]: - contacts = self.get_contacts(account, jid) - if group in contacts[0].groups: - group_contacts += contacts - return group_contacts - + def get_nb_online_total_contacts(self, accounts=[], groups=[]): '''Returns the number of online contacts and the total number of contacts''' @@ -423,9 +363,6 @@ class Contacts: return False return True - def get_jid_list(self, account): - return self._contacts[account].keys() - def __getattr__(self, attr_name): # Only called if self has no attr_name if hasattr(self._metacontact_manager, attr_name): @@ -459,6 +396,95 @@ class Contacts: def get_nb_role_total_gc_contacts(self, account, room_jid, role): return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role) + +class Contacts_New(): + + def __init__(self): + # list of contacts {jid1: [C1, C2]}, } one Contact per resource + self._contacts = {} + + def add_contact(self, contact): + if contact.jid not in self._contacts: + self._contacts[contact.jid] = [contact] + return + contacts = self._contacts[contact.jid] + # We had only one that was offline, remove it + if len(contacts) == 1 and contacts[0].show == 'offline': + # Do not use self.remove_contact: it deteles + # self._contacts[account][contact.jid] + contacts.remove(contacts[0]) + # If same JID with same resource already exists, use the new one + for c in contacts: + if c.resource == contact.resource: + self.remove_contact(c) + break + contacts.append(contact) + + def remove_contact(self, contact): + if contact.jid not in self._contacts: + return + if contact in self._contacts[contact.jid]: + self._contacts[contact.jid].remove(contact) + if len(self._contacts[contact.jid]) == 0: + del self._contacts[contact.jid] + + def remove_jid(self, jid): + '''Removes all contacts for a given jid''' + if jid not in self._contacts: + return + del self._contacts[jid] + + def get_contacts(self, jid): + '''Returns the list of contact instances for this jid.''' + if jid in self._contacts: + return self._contacts[jid] + else: + return [] + + def get_contact(self, jid, resource=None): + ### WARNING ### + # This function returns a *RANDOM* resource if resource = None! + # Do *NOT* use if you need to get the contact to which you + # send a message for example, as a bare JID in Jabber means + # highest available resource, which this function ignores! + '''Returns the contact instance for the given resource if it's given else + the first contact is no resource is given or None if there is not''' + if jid in self._contacts: + if not resource: + return self._contacts[jid][0] + for c in self._contacts[jid]: + if c.resource == resource: + return c + return None + + def iter_contacts(self): + for jid in self._contacts.keys(): + for contact in self._contacts[jid][:]: + yield contact + + def get_jid_list(self): + return self._contacts.keys() + + def get_contact_from_full_jid(self, fjid): + ''' Get Contact object for specific resource of given jid''' + barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid) + return self.get_contact(barejid, resource) + + def get_first_contact_from_jid(self, jid): + if jid in self._contacts: + return self._contacts[jid][0] + return None + + def get_contacts_from_group(self, group): + '''Returns all contacts in the given group''' + group_contacts = [] + for jid in self._contacts: + contacts = self.get_contacts(jid) + if group in contacts[0].groups: + group_contacts += contacts + return group_contacts + + class GC_Contacts(): @@ -513,7 +539,7 @@ class GC_Contacts(): nb_role += 1 nb_total += 1 return nb_role, nb_total - + class MetacontactManager(): diff --git a/test/unit/test_account.py b/test/unit/test_account.py index faa577b7f..d655aae2f 100644 --- a/test/unit/test_account.py +++ b/test/unit/test_account.py @@ -11,9 +11,11 @@ from common.account import Account class Test(unittest.TestCase): def testInstantiate(self): - account = Account(gc_contacts=None) + account = Account(name='MyAcc', contacts=None, gc_contacts=None) + self.assertEquals('MyAcc', account.name) self.assertTrue(account.gc_contacts is None) + self.assertTrue(account.contacts is None) if __name__ == "__main__": unittest.main() \ No newline at end of file diff --git a/test/unit/test_contacts.py b/test/unit/test_contacts.py index ee9410f85..3cd4d5110 100644 --- a/test/unit/test_contacts.py +++ b/test/unit/test_contacts.py @@ -98,6 +98,32 @@ class TestContacts(unittest.TestCase): # Not yet implemented to remain backwart compatible # self.assertEqual(contact, copy, msg="Must be equal") - + def test_legacy_accounts_handling(self): + self.contacts.add_account("one") + self.contacts.add_account("two") + + self.contacts.change_account_name("two", "old") + self.contacts.remove_account("one") + + self.assertEqual(["old"], self.contacts.get_accounts()) + + def test_legacy_contacts_from_groups(self): + jid1 = "test1@gajim.org" + jid2 = "test2@gajim.org" + account = "account" + group = "GroupA" + + contact1 = self.contacts.create_contact(jid=jid1, account=account, + groups=[group]) + self.contacts.add_contact(account, contact1) + + contact2 = self.contacts.create_contact(jid=jid2, account=account, + groups=[group]) + self.contacts.add_contact(account, contact2) + + self.assertEqual(2, len(self.contacts.get_contacts_from_group(account, group))) + self.assertEqual(0, len(self.contacts.get_contacts_from_group(account, ''))) + + if __name__ == "__main__": unittest.main() \ No newline at end of file From 8203211e74fcdfc45ece1d82b730630287ca6f60 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Wed, 11 Nov 2009 21:38:39 +0100 Subject: [PATCH 18/19] Inject account object instead of account string into contact instances. Each contact has now access to the account object instead of the account string. --- src/common/account.py | 6 +++++- src/common/contacts.py | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/common/account.py b/src/common/account.py index 387559199..1c080cad8 100644 --- a/src/common/account.py +++ b/src/common/account.py @@ -25,4 +25,8 @@ class Account(object): self.contacts = contacts self.gc_contacts = gc_contacts - \ No newline at end of file + def __repr__(self): + return self.name + + def __hash__(self): + return self.name.__hash__() \ No newline at end of file diff --git a/src/common/contacts.py b/src/common/contacts.py index b766e3d64..b46523a88 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -229,7 +229,7 @@ class Contacts: sub='', ask='', resource='', priority=0, keyID='', client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None, composing_xep=None, mood={}, tune={}, activity={}): - + account = self._accounts.get(account, account) # Use Account object if available return Contact(jid=jid, account=account, name=name, groups=groups, show=show, status=status, sub=sub, ask=ask, resource=resource, priority=priority, keyID=keyID, client_caps=client_caps, our_chatstate=our_chatstate, @@ -239,6 +239,7 @@ class Contacts: def create_self_contact(self, jid, account, resource, show, status, priority, keyID=''): conn = common.gajim.connections[account] nick = common.gajim.nicks[account] + account = self._accounts.get(account, account) # Use Account object if available return self.create_contact(jid=jid, account=account, name=nick, groups=['self_contact'], show=show, status=status, sub='both', ask='none', priority=priority, keyID=keyID, @@ -246,6 +247,7 @@ class Contacts: activity=conn.activity) def create_not_in_roster_contact(self, jid, account, resource='', name='', keyID=''): + account = self._accounts.get(account, account) # Use Account object if available return self.create_contact(jid=jid, account=account, resource=resource, name=name, groups=[_('Not in Roster')], show='not in roster', status='', sub='none', keyID=keyID) @@ -372,6 +374,7 @@ class Contacts: def create_gc_contact(self, room_jid, account, name='', show='', status='', role='', affiliation='', jid='', resource=''): + account = self._accounts.get(account, account) # Use Account object if available return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid, resource) From af3af5bec862d593d391540ca984dfd376b62a74 Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Wed, 11 Nov 2009 23:14:51 +0100 Subject: [PATCH 19/19] Remove caps which have not been seen for three months from the db. Thanks Asterix for the initial version of this patch! --- src/common/caps.py | 62 +++++++++++++++++++++++---------------- src/common/check_paths.py | 3 +- src/common/defs.py | 2 +- src/common/logger.py | 19 ++++++++++-- src/common/optparser.py | 22 ++++++++++++++ test/unit/test_caps.py | 4 +-- 6 files changed, 79 insertions(+), 33 deletions(-) diff --git a/src/common/caps.py b/src/common/caps.py index bb52bdcf4..a4c3ffb29 100644 --- a/src/common/caps.py +++ b/src/common/caps.py @@ -40,6 +40,10 @@ from common.xmpp import NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES # Features where we cannot safely assume that the other side supports them FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION] +# Query entry status codes +NEW = 0 +QUERIED = 1 +CACHED = 2 # got the answer ################################################################################ ### Public API of this module @@ -58,7 +62,7 @@ def client_supports(client_caps, requested_feature): supported_features = cache_item.features if requested_feature in supported_features: return True - elif supported_features == [] and cache_item.queried in (0, 1): + elif supported_features == [] and cache_item.status in (NEW, QUERIED): # assume feature is supported, if we don't know yet, what the client # is capable of return requested_feature not in FEATURE_BLACKLIST @@ -168,8 +172,8 @@ class AbstractClientCaps(object): def _is_hash_valid(self, identities, features, dataforms): ''' To be implemented by subclassess ''' raise NotImplementedError() - - + + class ClientCaps(AbstractClientCaps): ''' The current XEP-115 implementation ''' @@ -188,7 +192,7 @@ class ClientCaps(AbstractClientCaps): computed_hash = compute_caps_hash(identities, features, dataforms=dataforms, hash_method=self._hash_method) return computed_hash == self._hash - + class OldClientCaps(AbstractClientCaps): ''' Old XEP-115 implemtation. Kept around for background competability. ''' @@ -204,7 +208,7 @@ class OldClientCaps(AbstractClientCaps): def _is_hash_valid(self, identities, features, dataforms): return True - + class NullClientCaps(AbstractClientCaps): ''' @@ -220,7 +224,7 @@ class NullClientCaps(AbstractClientCaps): def _lookup_in_cache(self, caps_cache): # lookup something which does not exist to get a new CacheItem created cache_item = caps_cache[('dummy', '')] - assert cache_item.queried == 0 + assert cache_item.status != CACHED return cache_item def _discover(self, connection, jid): @@ -248,7 +252,7 @@ class CapsCache(object): # another object, and we will have plenty of identical long # strings. therefore we can cache them __names = {} - + def __init__(self, hash_method, hash_, logger): # cached into db self.hash_method = hash_method @@ -257,12 +261,8 @@ class CapsCache(object): self._identities = [] self._logger = logger - # not cached into db: - # have we sent the query? - # 0 == not queried - # 1 == queried - # 2 == got the answer - self.queried = 0 + self.status = NEW + self._recently_seen = False def _get_features(self): return self._features @@ -304,19 +304,28 @@ class CapsCache(object): self.features = features self._logger.add_caps_entry(self.hash_method, self.hash, identities, features) + self.status = CACHED + + def update_last_seen(self): + if not self._recently_seen: + self._recently_seen = True + self._logger.update_caps_time(self.hash_method, self.hash) self.__CacheItem = CacheItem self.logger = logger def initialize_from_db(self): - # get data from logger... - if self.logger is not None: - for hash_method, hash_, identities, features in \ - self.logger.iter_caps_data(): - x = self[(hash_method, hash_)] - x.identities = identities - x.features = features - x.queried = 2 + self._remove_outdated_caps() + for hash_method, hash_, identities, features in \ + self.logger.iter_caps_data(): + x = self[(hash_method, hash_)] + x.identities = identities + x.features = features + x.status = CACHED + + def _remove_outdated_caps(self): + '''Removes outdated values from the db''' + self.logger.clean_caps_table() def __getitem__(self, caps): if caps in self.__cache: @@ -336,13 +345,14 @@ class CapsCache(object): lookup_cache_item = client_caps.get_cache_lookup_strategy() q = lookup_cache_item(self) - if q.queried == 0: + if q.status == NEW: # do query for bare node+hash pair # this will create proper object - q.queried = 1 + q.status = QUERIED discover = client_caps.get_discover_strategy() discover(connection, jid) - + else: + q.update_last_seen() ################################################################################ ### Caps network coding @@ -391,7 +401,7 @@ class ConnectionCaps(object): client_caps = OldClientCaps(caps_hash, node) else: client_caps = ClientCaps(caps_hash, node, hash_method) - + capscache.query_client_of_jid_if_unknown(self, jid, client_caps) contact.client_caps = client_caps @@ -409,7 +419,7 @@ class ConnectionCaps(object): lookup = contact.client_caps.get_cache_lookup_strategy() cache_item = lookup(capscache) - if cache_item.queried == 2: + if cache_item.status == CACHED: return else: validate = contact.client_caps.get_hash_validation_strategy() diff --git a/src/common/check_paths.py b/src/common/check_paths.py index 0a9dcb83e..c4864fc78 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -91,7 +91,8 @@ def create_log_db(): CREATE TABLE caps_cache ( hash_method TEXT, hash TEXT, - data BLOB); + data BLOB, + last_seen INTEGER); CREATE TABLE rooms_last_message_time( jid_id INTEGER PRIMARY KEY UNIQUE, diff --git a/src/common/defs.py b/src/common/defs.py index 2886838a1..6640c3b32 100644 --- a/src/common/defs.py +++ b/src/common/defs.py @@ -27,7 +27,7 @@ docdir = '../' datadir = '../' localedir = '../po' -version = '0.12.5.8-dev' +version = '0.13.0.1-dev' import sys, os.path for base in ('.', 'common'): diff --git a/src/common/logger.py b/src/common/logger.py index d4af560f1..c96282743 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -838,14 +838,27 @@ class Logger: gzip.close() data = string.getvalue() self.cur.execute(''' - INSERT INTO caps_cache ( hash_method, hash, data ) - VALUES (?, ?, ?); - ''', (hash_method, hash_, buffer(data))) # (1) -- note above + INSERT INTO caps_cache ( hash_method, hash, data, last_seen ) + VALUES (?, ?, ?, ?); + ''', (hash_method, hash_, buffer(data), int(time.time()))) + # (1) -- note above try: self.con.commit() except sqlite.OperationalError, e: print >> sys.stderr, str(e) + def update_caps_time(self, method, hash_): + sql = '''UPDATE caps_cache SET last_seen = %d + WHERE hash_method = "%s" and hash = "%s"''' % \ + (int(time.time()), method, hash_) + self.simple_commit(sql) + + def clean_caps_table(self): + '''Remove caps which was not seen for 3 months''' + sql = '''DELETE FROM caps_cache WHERE last_seen < %d''' % \ + int(time.time() - 3*30*24*3600) + self.simple_commit(sql) + def replace_roster(self, account_name, roster_version, roster): ''' Replace current roster in DB by a new one. accout_name is the name of the account to change diff --git a/src/common/optparser.py b/src/common/optparser.py index 783e332a5..f9f738d5e 100644 --- a/src/common/optparser.py +++ b/src/common/optparser.py @@ -29,6 +29,7 @@ import os import locale import re +from time import time from common import gajim from common import helpers from common import caps @@ -218,6 +219,8 @@ class OptionsParser: self.update_config_to_01257() if old < [0, 12, 5, 8] and new >= [0, 12, 5, 8]: self.update_config_to_01258() + if old < [0, 13, 0, 1] and new >= [0, 13, 0, 1]: + self.update_config_to_01301() gajim.logger.init_vars() gajim.config.set('version', new_version) @@ -817,4 +820,23 @@ class OptionsParser: 'proxy.jabber.ru', 'proxy.jabbim.cz']) gajim.config.set('version', '0.12.5.8') + def update_config_to_01301(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + ALTER TABLE caps_cache + ADD last_seen INTEGER default %d; + ''' % int(time()) + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.13.0.1') + # vim: se ts=3: diff --git a/test/unit/test_caps.py b/test/unit/test_caps.py index 160996b6a..a143f225c 100644 --- a/test/unit/test_caps.py +++ b/test/unit/test_caps.py @@ -66,9 +66,9 @@ class TestCapsCache(CommonCapsTest): def test_initialize_from_db(self): ''' Read cashed dummy data from db ''' - self.assertEqual(self.cc[self.client_caps].queried, 0) + self.assertEqual(self.cc[self.client_caps].status, caps.NEW) self.cc.initialize_from_db() - self.assertEqual(self.cc[self.client_caps].queried, 2) + self.assertEqual(self.cc[self.client_caps].status, caps.CACHED) def test_preload_triggering_query(self): ''' Make sure that preload issues a disco '''