diff --git a/src/chat_control.py b/src/chat_control.py index 50a43552f..04d6abc00 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -2774,7 +2774,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/account.py b/src/common/account.py new file mode 100644 index 000000000..1c080cad8 --- /dev/null +++ b/src/common/account.py @@ -0,0 +1,32 @@ +# -*- 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, name, contacts, gc_contacts): + self.name = name + self.contacts = contacts + self.gc_contacts = gc_contacts + + def __repr__(self): + return self.name + + def __hash__(self): + return self.name.__hash__() \ No newline at end of file diff --git a/src/common/caps.py b/src/common/caps.py index 348eb6813..b4a8d2a05 100644 --- a/src/common/caps.py +++ b/src/common/caps.py @@ -40,13 +40,34 @@ 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 +################################################################################ 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.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 + 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 +139,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 @@ -147,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 ''' @@ -167,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. ''' @@ -183,7 +208,7 @@ class OldClientCaps(AbstractClientCaps): def _is_hash_valid(self, identities, features, dataforms): return True - + class NullClientCaps(AbstractClientCaps): ''' @@ -199,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 != 2 + assert cache_item.status != CACHED return cache_item def _discover(self, connection, jid): @@ -236,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 @@ -283,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: @@ -315,13 +345,18 @@ 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 +################################################################################ class ConnectionCaps(object): ''' @@ -366,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 @@ -384,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/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() diff --git a/src/common/contacts.py b/src/common/contacts.py index e35f41eba..d04486f8b 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -29,18 +29,24 @@ ## import common.gajim +import caps +from account import Account - -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, - composing_xep, chatstate, client_caps=None): - + 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.show = show self.status = status self.name = name @@ -80,35 +86,21 @@ class CommonContact(object): # 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): '''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 - self.groups = groups + self.groups = [i for i in set(groups)] # filter duplicate values self.sub = sub self.ask = ask @@ -184,11 +176,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 @@ -201,153 +193,112 @@ 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''' 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._accounts = {} + + 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) - # For meta contacts: - self._metacontacts_tags = {} - - 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] - - def change_contact_jid(self, old_jid, new_jid, account): - if account not in self._contacts: - return - if old_jid not in self._contacts[account]: - return - self._contacts[account][new_jid] = [] - for _contact in self._contacts[account][old_jid]: - _contact.jid = new_jid - self._contacts[account][new_jid].append(_contact) - del self._contacts[account][old_jid] - - def add_account(self, account): - self._contacts[account] = {} - self._gc_contacts[account] = {} - if account not in self._metacontacts_tags: - self._metacontacts_tags[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._gc_contacts[account] - del self._metacontacts_tags[account] + del self._accounts[account] + self._metacontact_manager.remove_account(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={}): - - # 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, name=name, groups=groups_unique, show=show, - status=status, sub=sub, ask=ask, resource=resource, priority=priority, + 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, 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] + 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, + resource=resource, mood=conn.mood, tune=conn.tune, + 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) 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): - # 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] - - def clear_contacts(self, account): - self._contacts[account] = {} + 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: - # 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.''' - 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 change_contact_jid(self, old_jid, new_jid, account): + return self._accounts[account].change_contact_jid(old_jid, new_jid) def get_highest_prio_contact_from_contacts(self, contacts): if not contacts: @@ -366,21 +317,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''' @@ -419,11 +356,227 @@ class Contacts: nbr_total += 1 return nbr_online, nbr_total + 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 __getattr__(self, attr_name): + # Only called if self has no attr_name + if hasattr(self._metacontact_manager, attr_name): + return getattr(self._metacontact_manager, attr_name) + else: + raise AttributeError(attr_name) + + 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) + + def add_gc_contact(self, account, gc_contact): + return self._accounts[account].gc_contacts.add_gc_contact(gc_contact) + + def remove_gc_contact(self, account, gc_contact): + return self._accounts[account].gc_contacts.remove_gc_contact(gc_contact) + + def remove_room(self, account, room_jid): + return self._accounts[account].gc_contacts.remove_room(room_jid) + + def get_gc_list(self, account): + return self._accounts[account].gc_contacts.get_gc_list() + + def get_nick_list(self, account, room_jid): + return self._accounts[account].gc_contacts.get_nick_list(room_jid) + + def get_gc_contact(self, 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 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 + + def change_contact_jid(self, old_jid, new_jid): + if old_jid not in self._contacts: + return + self._contacts[new_jid] = [] + for _contact in self._contacts[old_jid]: + _contact.jid = new_jid + self._contacts[new_jid].append(_contact) + del self._contacts[old_jid] + + +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 room_jid not in self._rooms: + return 0, 0 + nb_role = nb_total = 0 + 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 + + +class MetacontactManager(): + + 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] + 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(): + def _get_new_metacontacts_tag(self, jid): + if not jid in self._metacontacts_tags: return jid #FIXME: can this append ? assert False @@ -434,7 +587,7 @@ class Contacts: 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 @@ -445,19 +598,19 @@ class Contacts: 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: @@ -471,6 +624,9 @@ class Contacts: 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]: @@ -484,7 +640,7 @@ class Contacts: 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) @@ -516,7 +672,7 @@ class Contacts: '''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): @@ -530,7 +686,7 @@ class Contacts: 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''' @@ -538,8 +694,8 @@ class Contacts: 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 @@ -613,98 +769,7 @@ class Contacts: 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] - - 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 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, - 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='', - role='', affiliation='', jid='', resource=''): - return GC_Contact(room_jid, name, show, status, role, affiliation, jid, - resource) - - def add_gc_contact(self, account, gc_contact): - # 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/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/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/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/src/dialogs.py b/src/dialogs.py index f79a8d374..1e4e6266b 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, 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/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..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 @@ -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) @@ -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 99733ff92..0898ae39f 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -352,11 +352,9 @@ class Interface: # Ignore offline presence of unknown self resource if new_show < 2: return - contact1 = gajim.contacts.create_contact(jid=ji, - 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) @@ -541,8 +539,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') % { @@ -620,8 +618,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) @@ -898,7 +896,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: @@ -1221,8 +1219,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: @@ -1361,9 +1359,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, 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] @@ -2233,7 +2230,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( @@ -2577,7 +2574,8 @@ class Interface: account): # Join new groupchat if minimize: - contact = gajim.contacts.create_contact(jid=room_jid, name=nick) + #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 self.roster.add_groupchat(room_jid, account) @@ -2600,7 +2598,8 @@ 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) + # 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: mw = self.msg_win_mgr.create_window(contact, account, @@ -2610,7 +2609,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 diff --git a/src/roster_window.py b/src/roster_window.py index ce8c41261..2493f4dba 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -688,9 +688,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) @@ -822,7 +820,8 @@ class RosterWindow: else: name = jid.split('@')[0] # New groupchat - contact = gajim.contacts.create_contact(jid=jid, name=name, + #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) self.add_contact(jid, account) @@ -857,7 +856,8 @@ 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, + #TRANSP + 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) @@ -998,9 +998,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, 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 @@ -1788,7 +1787,8 @@ class RosterWindow: if gajim.jid_is_transport(jid): array[jid]['groups'] = [_('Transports')] - contact1 = gajim.contacts.create_contact(jid=ji, name=name, + #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'], resource=resource, keyID=keyID) @@ -1935,7 +1935,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) @@ -2525,7 +2525,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, @@ -2554,11 +2554,10 @@ class RosterWindow: show = roster.getShow(jid+'/'+resource) if not show: show = 'online' - contact = gajim.contacts.create_contact(jid=jid, - 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 @@ -5116,7 +5115,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..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, 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 dec454c18..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 = contacts.Contact(jid=jid, 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) @@ -506,7 +507,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/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 10d1ab488..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) @@ -190,7 +186,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/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..d655aae2f --- /dev/null +++ b/test/unit/test_account.py @@ -0,0 +1,21 @@ +''' +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(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_caps.py b/test/unit/test_caps.py index 787047c1f..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 ''' @@ -113,25 +113,23 @@ class TestClientCaps(CommonCapsTest): "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=") def test_client_supports(self): - contact = Contact(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): diff --git a/test/unit/test_contacts.py b/test/unit/test_contacts.py index 401f35620..3cd4d5110 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,99 @@ 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(room_jid="confernce@gajim.org", account="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") + + 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