diff options
-rw-r--r-- | src/contact.py | 196 | ||||
-rw-r--r-- | src/core.py | 187 | ||||
-rw-r--r-- | src/roster.py | 297 | ||||
-rw-r--r-- | src/tabs.py | 123 | ||||
-rw-r--r-- | src/windows.py | 31 |
5 files changed, 400 insertions, 434 deletions
diff --git a/src/contact.py b/src/contact.py index 66f8c453..48f5d751 100644 --- a/src/contact.py +++ b/src/contact.py @@ -20,43 +20,28 @@ class Resource(object): Defines a roster item. It's a precise resource. """ - def __init__(self, jid): + def __init__(self, jid, data): self._jid = JID(jid) # Full jid - self._status = '' - self._presence = 'unavailable' - self._priority = 0 + self._data = data @property def jid(self): return self._jid - def __repr__(self): - return '%s' % self._jid - @property def priority(self): - return self._priority - - @priority.setter - def priority(self, value): - assert isinstance(value, int) - self._priority = value + return self._data['priority'] @property def presence(self): - return self._presence - - @presence.setter - def presence(self, value): - self._presence = value + return self._data['show'] @property def status(self): - return self._status + return self._data['status'] - @status.setter - def status(self, value): - self._status = value + def __repr__(self): + return '<%s>' % self._jid class Contact(object): """ @@ -64,19 +49,17 @@ class Contact(object): This class contains zero or more Resource object and useful methods to get the resource with the highest priority, etc """ - def __init__(self, bare_jid): - self._jid = bare_jid - self._resources = [] - self._folded = True # Folded by default - self._display_name = None - self._subscription = 'none' - self._ask = None - self._groups = [] # a list of groups the contact is in + def __init__(self, item): + """ + item: a SleekXMPP RosterItem pointing to that contact + """ + self.__item = item + self.folded = True # Folded by default @property def groups(self): - """Groups the contact is in""" - return self._groups + """Name of the groups the contact is in""" + return self.__item['groups'] or ['none'] @property def resources(self): @@ -86,98 +69,101 @@ class Contact(object): @property def bare_jid(self): """The bare_jid or the contact""" - return self._jid - - def get_highest_priority_resource(self): - """ - Return the resource with the highest priority - """ - ret = None - for resource in self._resources: - if not ret or ret.priority < resource.priority: - ret = resource - return ret - - def add_resource(self, resource): - """ - Called, for example, when a new resource get offline - (the first, or any subsequent one) - """ - def f(o): - return o.priority - self._resources.append(resource) - self._resources = sorted(self._resources, key=f, reverse=True) + return self.__item.jid - def remove_resource(self, resource): - """ - Called, for example, when one resource goes offline. - """ - self._resources.remove(resource) + @property + def name(self): + return self.__item['name'] or '' - def remove_resource_by_fulljid(self, fulljid): - """ - Like 'remove_resource' but just by knowing the full jid - """ - for resource in self._resources: - if resource.jid == fulljid: - self._resources.remove(resource) - return - assert False + @property + def ask(self): + if self.__item['pending_out']: + return 'asked' - def get_resource_by_fulljid(self, fulljid): - """ - Return the resource with the given fulljid - """ - for resource in self._resources: - if resource.jid.full == fulljid: - return resource - return None + @property + def pending_in(self): + return self.__item['pending_in'] - def toggle_folded(self): - """ - Fold if it's unfolded, and vice versa - """ - self._folded = not self._folded + @pending_in.setter + def pending_in(self, value): + self.__item['pending_in'] = value @property - def name(self): - return self._display_name + def pending_out(self): + return self.__item['pending_out'] - @name.setter - def name(self, value): - self._display_name = value + @pending_out.setter + def pending_out(self, value): + self.__item['pending_out'] = value @property - def ask(self): - return self._ask - - @ask.setter - def ask(self, value): - self._ask = value + def resources(self): + """List of the available resources as Resource objects""" + return [Resource('%s/%s' % (self.bare_jid, key), self.__item.resources[key]) + for key in self.__item.resources] @property def subscription(self): - return self._subscription + return self.__item['subscription'] - @subscription.setter - def subscription(self, value): - self._subscription = value + def __contains__(self, value): + return value in self.__item.resources or JID(value).resource in self.__item.resources - def get_nb_resources(self): - """ - Get the number of connected resources - """ - return len(self._resources) + def __len__(self): + """Number of resources""" + return len(self.__item.resources) + + def __bool__(self): + """This contacts exists even when he has no resources""" + return True + + def __getitem__(self, key): + """Return the corresponding Resource object, or None""" + res = JID(key).resource + resources = self.__item.resources + item = resources.get(res, None) or resources.get(key, None) + return Resource(key, item) if item else None + + def subscribe(self): + """Subscribe to this JID""" + self.__item.subscribe() + + def authorize(self): + """Authorize this JID""" + self.__item.authorize() + + def unauthorize(self): + """Unauthorize this JID""" + self.__item.unauthorize() + + def unsubscribe(self): + """Unsubscribe from this JID""" + self.__item.unsubscribe() + + def get(self, key, default=None): + """Same as __getitem__, but with a configurable default""" + return self[key] or default def get_resources(self): + """Return all resources, sorted by priority """ + compare_resources = lambda x: x.priority + return sorted(self.resources, key=compare_resources) + + def get_highest_priority_resource(self): + """Return the resource with the highest priority""" + resources = self.get_resources() + if resources: + return resources[-1] + return None + + def toggle_folded(self): """ - Return all resources, sorted by priority + Fold if it's unfolded, and vice versa """ - compare_resources = lambda x: x.priority - return sorted(self._resources, key=compare_resources) + self.folded = not self.folded def __repr__(self): - ret = '<Contact: %s' % self._jid - for resource in self._resources: + ret = '<Contact: %s' % self.bare_jid + for resource in self.resources: ret += '\n\t\t%s' % resource return ret + ' />\n' diff --git a/src/core.py b/src/core.py index 627f7f5c..f7447863 100644 --- a/src/core.py +++ b/src/core.py @@ -92,6 +92,9 @@ class Core(object): self.events = events.EventHandler() self.xmpp = singleton.Singleton(connection.Connection) self.xmpp.core = self + roster.set_node(self.xmpp.client_roster) + roster.set_mucs(self.xmpp.plugin['xep_0045'].rooms) + roster.set_self_jid(self.xmpp.boundjid.bare) self.paused = False self.remote_fifo = None # a unique buffer used to store global informations @@ -194,7 +197,10 @@ class Core(object): self.xmpp.add_event_handler("got_offline" , self.on_got_offline) self.xmpp.add_event_handler("roster_update", self.on_roster_update) self.xmpp.add_event_handler("changed_status", self.on_presence) - self.xmpp.add_event_handler("changed_subscription", self.on_changed_subscription) + self.xmpp.add_event_handler("roster_subscription_request", self.on_subscription_request) + self.xmpp.add_event_handler("roster_subscription_authorized", self.on_subscription_authorized) + self.xmpp.add_event_handler("roster_subscription_remove", self.on_subscription_remove) + self.xmpp.add_event_handler("roster_subscription_removed", self.on_subscription_removed) self.xmpp.add_event_handler("message_xform", self.on_data_form) self.xmpp.add_event_handler("chatstate_active", self.on_chatstate_active) self.xmpp.add_event_handler("chatstate_composing", self.on_chatstate_composing) @@ -462,7 +468,7 @@ class Core(object): if txt.endswith(' '): n += 1 if n == 2: - return the_input.auto_completion([contact.bare_jid for contact in roster.get_contacts()], '') + return the_input.auto_completion([jid for jid in roster.jids()], '') elif n == 3: rooms = [] for tab in self.tabs: @@ -597,53 +603,37 @@ class Core(object): def on_got_offline(self, presence): jid = presence['from'] - contact = roster.get_contact_by_jid(jid.bare) logger.log_roster_change(jid.bare, 'got offline') - if not contact: - return - resource = contact.get_resource_by_fulljid(jid.full) - if not resource: - return # If a resource got offline, display the message in the conversation with this # precise resource. - self.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x191}offline' % (resource.jid.full)) - contact.remove_resource(resource) - # Display the message in the conversation with the bare JID only if that was - # the only resource online (i.e. now the contact is completely disconnected) - if not contact.get_highest_priority_resource(): # No resource left: that was the last one - self.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x191}offline' % (jid.bare)) - self.information('\x193}%s \x195}is \x191}offline' % (resource.jid.bare), "Roster") + if jid.resource: + self.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x191}offline' % (jid.full)) + self.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x191}offline' % (jid.bare)) + self.information('\x193}%s \x195}is \x191}offline' % (jid.bare), 'Roster') if isinstance(self.current_tab(), tabs.RosterInfoTab): self.refresh_window() def on_got_online(self, presence): jid = presence['from'] - contact = roster.get_contact_by_jid(jid.bare) - if not contact: - # Todo, handle presence comming from contacts not in roster + contact = roster[jid.bare] + if contact is None: + # Todo, handle presence coming from contacts not in roster return logger.log_roster_change(jid.bare, 'got online') - resource = contact.get_resource_by_fulljid(jid.full) - assert not resource - resource = Resource(jid.full) + resource = Resource(jid.full, { + 'priority': presence.get_priority() or 0, + 'status': presence['status'], + 'show': presence['show'], + }) self.events.trigger('normal_presence', presence, resource) - status = presence['type'] - status_message = presence['status'] - priority = presence.getPriority() or 0 - resource.status = status_message - resource.presence = status - resource.priority = priority self.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x194}online' % (jid.full)) - if not contact.get_highest_priority_resource(): - # No connected resource yet: the user's just connecting - if time.time() - self.connection_time > 12: - # We do not display messages if we recently logged in - if status_message: - self.information("\x193}%s \x195}is \x194}online\x195} (\x19o%s\x195})" % (resource.jid.bare, status_message), "Roster") - else: - self.information("\x193}%s \x195}is \x194}online\x195}" % resource.jid.bare, "Roster") + if time.time() - self.connection_time > 20: + # We do not display messages if we recently logged in + if presence['status']: + self.information("\x193}%s \x195}is \x194}online\x195} (\x19o%s\x195})" % (resource.jid.bare, presence['status']), "Roster") + else: + self.information("\x193}%s \x195}is \x194}online\x195}" % resource.jid.bare, "Roster") self.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x194}online' % (jid.bare)) - contact.add_resource(resource) if isinstance(self.current_tab(), tabs.RosterInfoTab): self.refresh_window() @@ -693,7 +683,7 @@ class Core(object): self.information(_("Your JID is %s") % self.xmpp.boundjid.full) if not self.xmpp.anon: # request the roster - self.xmpp.getRoster() + self.xmpp.get_roster() # send initial presence if config.get('send_initial_presence', 'true').lower() == 'true': pres = self.xmpp.make_presence() @@ -860,8 +850,8 @@ class Core(object): conversation = self.get_tab_of_conversation_with_jid(jid, create=True) self.events.trigger('conversation_msg', message, conversation) body = xhtml.get_body_from_message_stanza(message) - if roster.get_contact_by_jid(jid.bare): - remote_nick = roster.get_contact_by_jid(jid.bare).name or jid.user + if jid.bare in roster: + remote_nick = roster[jid.bare].name or jid.user else: remote_nick = jid.user delay_tag = message.find('{urn:xmpp:delay}delay') @@ -889,20 +879,10 @@ class Core(object): def on_presence(self, presence): jid = presence['from'] - contact = roster.get_contact_by_jid(jid.bare) - if not contact: - resource = None - else: - resource = contact.get_resource_by_fulljid(jid.full) - self.events.trigger('normal_presence', presence, resource) - if not resource: + contact = roster[jid.bare] + self.events.trigger('normal_presence', presence, contact[jid.full]) + if contact is None: return - status = presence['type'] - status_message = presence['status'] - priority = presence.getPriority() or 0 - resource.presence = status - resource.priority = priority - resource.status = status_message tab = self.get_tab_of_conversation_with_jid(jid, create=False) if isinstance(self.current_tab(), tabs.RosterInfoTab): self.refresh_window() @@ -912,60 +892,64 @@ class Core(object): def on_roster_update(self, iq): """ - A subscription changed, or we received a roster item - after a roster request, etc - """ - for item in iq.findall('{jabber:iq:roster}query/{jabber:iq:roster}item'): - jid = item.attrib['jid'] - contact = roster.get_contact_by_jid(jid) - if not contact: - contact = Contact(jid) - roster.add_contact(contact, jid) - if 'ask' in item.attrib: - contact.ask = item.attrib['ask'] - else: - contact.ask = None - if 'name' in item.attrib: - contact.name = item.attrib['name'] + The roster was received. + """ + for item in iq['roster']: + jid = item['jid'] + if item['subscription'] == 'remove': + del roster[jid] else: - contact.name = None - if item.attrib['subscription']: - contact.subscription = item.attrib['subscription'] - groups = item.findall('{jabber:iq:roster}group') - roster.edit_groups_of_contact(contact, [group.text for group in groups]) - if item.attrib['subscription'] == 'remove': - roster.remove_contact(contact.bare_jid) + roster.update_contact_groups(jid) if isinstance(self.current_tab(), tabs.RosterInfoTab): self.refresh_window() - def on_changed_subscription(self, presence): - """ - Triggered whenever a presence stanza with a type of subscribe, subscribed, unsubscribe, or unsubscribed is received. - """ + def on_subscription_request(self, presence): + """subscribe received""" jid = presence['from'].bare - contact = roster.get_contact_by_jid(jid) - if presence['type'] == 'subscribe': - if not contact: - contact = Contact(jid) - roster.add_contact(contact, jid) - log.debug("CONTACT: %s", contact) - if contact.subscription in ('from', 'both'): - log.debug('FROM OR BOTH') - return - elif contact.subscription in ('to'): - log.debug('TO') - self.xmpp.sendPresence(pto=jid, ptype='subscribed') - self.xmpp.sendPresence(pto=jid, ptype='') - return - roster.edit_groups_of_contact(contact, []) - contact.ask = 'asked' + contact = roster[jid] + if contact.subscription in ('from', 'both'): + return + elif contact.subscription == 'to': + self.xmpp.sendPresence(pto=jid, ptype='subscribed') + self.xmpp.sendPresence(pto=jid) + else: + roster.update_contact_groups(contact) + contact.pending_in = True + self.information('%s wants to subscribe to your presence' % jid, 'Roster') self.get_tab_by_number(0).state = 'highlight' - self.information('%s wants to subscribe to your presence'%jid, 'Roster') - elif presence['type'] == 'unsubscribed': - self.information('%s unsubscribed you from his presence'%jid, 'Roster') - elif presence['type'] == 'unsubscribe': - self.information('%s unsubscribed from your presence'%jid, 'Roster') + if isinstance(self.current_tab(), tabs.RosterInfoTab): + self.refresh_window() + + def on_subscription_authorized(self, presence): + """subscribed received""" + jid = presence['from'].bare + contact = roster[jid] + if contact.pending_out: + contact.pending_out = False + if isinstance(self.current_tab(), tabs.RosterInfoTab): + self.refresh_window() + + def on_subscription_remove(self, presence): + """unsubscribe received""" + jid = presence['from'].bare + contact = roster[jid] + if contact.subscription in ('to', 'both'): + self.information('%s does not want to receive your status anymore.' % jid, 'Roster') + self.get_tab_by_number(0).state = 'highlight' + if isinstance(self.current_tab(), tabs.RosterInfoTab): + self.refresh_window() + def on_subscription_removed(self, presence): + """unsubscribed received""" + jid = presence['from'].bare + contact = roster[jid] + if contact.subscription in ('both', 'from'): + self.information('%s does not want you to receive his status anymore.'%jid, 'Roster') + self.get_tab_by_number(0).state = 'highlight' + elif contact.pending_out: + self.information('%s rejected your contact proposal.' % jid, 'Roster') + self.get_tab_by_number(0).state = 'highlight' + contact.pending_out = False if isinstance(self.current_tab(), tabs.RosterInfoTab): self.refresh_window() @@ -1501,7 +1485,7 @@ class Core(object): if text.endswith(' '): n += 1 if n == 2: - return the_input.auto_completion([contact.bare_jid for contact in roster.get_contacts()], '') + return the_input.auto_completion([jid for jid in roster.jids()], '') elif n == 3: return the_input.auto_completion([status for status in possible_show], '') @@ -1761,7 +1745,7 @@ class Core(object): n = len(the_input.get_text().split()) if n > 2 or (n == 2 and the_input.get_text().endswith(' ')): return - return the_input.auto_completion([contact.bare_jid for contact in roster.get_contacts()], '', quotify=False) + return the_input.auto_completion([jid for jid in roster.jids()], '', quotify=False) def completion_list(self, the_input): muc_serv_list = [] @@ -2218,7 +2202,6 @@ class Core(object): for tab in self.tabs: if isinstance(tab, tabs.MucTab) and tab.joined: tab.command_part(msg) - roster.empty() self.save_config() # Ugly fix thanks to gmail servers self.xmpp.disconnect(reconnect) diff --git a/src/roster.py b/src/roster.py index 5f214bb0..957ed51a 100644 --- a/src/roster.py +++ b/src/roster.py @@ -16,140 +16,134 @@ from config import config from os import path as p from contact import Contact from sleekxmpp.xmlstream.stanzabase import JID +from sleekxmpp.exceptions import IqError class Roster(object): def __init__(self): - self._contact_filter = None # A tuple(function, *args) + """ + node: the RosterSingle from SleekXMPP + mucs: the dict from the SleekXMPP MUC plugin containing the joined mucs + """ + self.__node = None + self.__mucs = None + self.jid = None + self.contact_filter = None # A tuple(function, *args) # function to filter contacts, # on search, for example - self._contacts = {} # key = bare jid; value = Contact() - self._roster_groups = [] + self.folded_groups = config.get( + 'folded_roster_groups', + '', + section='var').split(':') + self.groups = {} + self.contacts = {} - def export(self, path): - if p.isfile(path): - return - try: - f = open(path, 'w+') - f.writelines([i + "\n" for i in self._contacts]) - f.close() - return True - except IOError: - return + def set_node(self, value): + self.__node = value - def empty(self): - self._contacts = {} - self._roster_groups = [] + def set_mucs(self, value): + self.__mucs = value - def add_contact(self, contact, jid): - """ - Add a contact to the contact list - """ - self._contacts[jid] = contact + def set_self_jid(self, value): + self.jid = value - def remove_contact(self, jid): - """ - Remove a contact from the contact list - """ - contact = self.get_contact_by_jid(jid) - for group in contact._groups: - self.remove_contact_from_group(group, contact) - del self._contacts[jid] + def get_groups(self): + """Return a list of the RosterGroups""" + return [group for group in self.groups.values() if group] - def get_contact_len(self): - """ - Return the number of contacts in this group - """ - return len(self._contacts.keys()) + def __getitem__(self, key): + """Get a Contact from his bare JID""" + key = JID(key).bare + if key in self.contacts and self.contacts[key] is not None: + return self.contacts[key] + if key in self.jids(): + contact = Contact(self.__node[key]) + self.contacts[key] = contact + return contact - def get_contact_by_jid(self, jid): - """ - Returns the contact with the given bare JID - """ - # Use only the bare jid - jid = JID(jid) - if jid.bare in self._contacts: - return self._contacts[jid.bare] - return None + def __setitem__(self, key, value): + """Set the a Contact value for the bare jid key""" + self.contacts[key] = value - def edit_groups_of_contact(self, contact, groups): - """ - Edit the groups the contact is in - Add or remove RosterGroup if needed - """ - # add the contact to each group he is in - # If the contact hasn't any group, we put her in - # the virtual default 'none' group - if not len(groups): - groups = ['none'] - for group in groups: - if group not in contact._groups: - # create the group if it doesn't exist yet - contact._groups.append(group) - self.add_contact_to_group(group, contact) - # remove the contact from each group he is not in - for group in contact._groups: - if group not in groups: - # the contact is not in the group anymore - contact._groups.remove(group) - self.remove_contact_from_group(group, contact) - - def remove_contact_from_group(self, group_name, contact): - """ - Remove the contact from the group. - Delete the group if this makes it empty - """ - for group in self._roster_groups: - if group.name == group_name: - group.remove_contact(contact) - if group.is_empty(): - self._roster_groups.remove(group) - return - - def add_contact_to_group(self, group_name, contact): - """ - Add the contact to the group. - Create the group if it doesn't already exist - """ - for group in self._roster_groups: - if group.name == group_name: - if not group.has_contact(contact): - group.add_contact(contact) - return - folded_groups = config.get('folded_roster_groups', '', section='var').split(':') - new_group = RosterGroup(group_name, folded=group_name in folded_groups) - self._roster_groups.append(new_group) - new_group.add_contact(contact) + def __delitem__(self, jid): + """Remove a contact from the roster""" + jid = JID(jid).bare + contact = self[jid] + if not contact: + return + for group in list(self.groups.values()): + group.remove(contact) + if not group: + del self.groups[group.name] + del self.contacts[contact.bare_jid] + if jid in self.jids(): + try: + self.__node[jid].send_presence(ptype='unavailable') + self.__node.remove(jid) + except IqError: + import traceback + log.debug('IqError when removing %s:\n%s', jid, traceback.format_exc()) - def get_groups(self): - """ - Returns the list of groups - """ - return self._roster_groups + def __iter__(self): + """Iterate over the jids of the contacts""" + return iter(self.contacts.values()) + + def __contains__(self, key): + """True if the bare jid is in the roster, false otherwise""" + return JID(key).bare in self.jids() + + def get_group(self, name): + """Return a group or create it if not present""" + if name in self.groups: + return self.groups[name] + self.groups[name] = RosterGroup(name, folded=name in self.folded_groups) + + def add(self, jid): + """Subscribe to a jid""" + self.__node.subscribe(jid) + + def jids(self): + """List of the contact JIDS""" + return [key for key in self.__node.keys() if key not in self.__mucs and key != self.jid] def get_contacts(self): """ - Return a list of all the contact + Return a list of all the contacts """ - return [contact for contact in self._contacts.values()] + return [self[jid] for jid in self.jids()] def save_to_config_file(self): """ Save various information to the config file e.g. the folded groups """ - folded_groups = ':'.join([group.name for group in self._roster_groups\ + folded_groups = ':'.join([group.name for group in self.groups.values()\ if group.folded]) log.debug('folded:%s\n' %folded_groups) config.set_and_save('folded_roster_groups', folded_groups, 'var') def get_nb_connected_contacts(self): - """ - Return the number of contacts connected - """ - length = 0 - for group in self._roster_groups: - length += group.get_nb_connected_contacts() - return length + n = 0 + for contact in self: + if contact.resources: + n += 1 + return n + + def update_contact_groups(self, contact): + """Regenerate the RosterGroups when receiving a contact update""" + if not isinstance(contact, Contact): + contact = self[contact] + if not contact: + return + for name, group in self.groups.items(): + if name in contact.groups and contact not in group: + group.add(contact) + elif contact in group and name not in contact.groups: + group.remove(contact) + + for group in contact.groups: + if not group in self.groups: + self.groups[group] = RosterGroup(group, folded=group in self.folded_groups) + self.groups[group].add(contact) def __len__(self): """ @@ -158,31 +152,43 @@ class Roster(object): """ length = 0 show_offline = config.get('roster_show_offline', 'false') == 'true' - for group in self._roster_groups: + for group in self.groups.values(): if not show_offline and group.get_nb_connected_contacts() == 0: continue - length += 1 # One for the group's line itself - if not group.folded: - for contact in group.get_contacts(self._contact_filter): + if not group in self.folded_groups: + for contact in group.get_contacts(self.contact_filter): # We do not count the offline contacts (depending on config) if not show_offline and\ - contact.get_nb_resources() == 0: + len(contact) == 0: continue length += 1 # One for the contact's line - if not contact._folded: + if not contact.folded: # One for each resource, if the contact is unfolded - length += contact.get_nb_resources() + length += len(contact) + length += 1 # One for the group's line itself return length def __repr__(self): ret = '== Roster:\nContacts:\n' - for contact in self._contacts: + for contact in self.contacts.values(): ret += '%s\n' % (contact,) ret += 'Groups\n' - for group in self._roster_groups: + for group in self.groups: ret += '%s\n' % (group,) return ret + '\n' + def export(self, path): + """Export a list of bare jids to a given file""" + if p.isfile(path): + return + try: + f = open(path, 'w+') + f.writelines([i + "\n" for i in self.contacts]) + f.close() + return True + except IOError: + return + PRESENCE_PRIORITY = {'unavailable': 0, 'xa': 1, 'away': 2, @@ -196,41 +202,38 @@ class RosterGroup(object): It can be Friends/Family etc, but also can be Online/Offline or whatever """ - def __init__(self, name, folded=False): - self._contacts = [] + def __init__(self, name, contacts=[], folded=False): + self.contacts = set(contacts) self.name = name self.folded = folded # if the group content is to be shown - def is_empty(self): - return len(self._contacts) == 0 + def __iter__(self): + """Iterate over the contacts""" + return iter(self.contacts) - def has_contact(self, contact): - """ - Return a bool, telling if the contact - is already in the group - """ - if contact in self._contacts: - return True - return False + def __repr__(self): + return '<Roster_group: %s; %s>' % (self.name, self.contacts) - def remove_contact(self, contact): - """ - Remove a Contact object from the list - """ - try: - self._contacts.remove(contact) - except ValueError: - pass + def __len__(self): + return len(self.contacts) - def add_contact(self, contact): + def __contains__(self, contact): """ - append a Contact object to the list + Return a bool, telling if the contact is in the group """ - assert isinstance(contact, Contact) - assert contact not in self._contacts - self._contacts.append(contact) + return contact in self.contacts + + def add(self, contact): + """Add a contact to the group""" + self.contacts.add(contact) + + def remove(self, contact): + """Remove a contact from the group if present""" + if contact in self.contacts: + self.contacts.remove(contact) def get_contacts(self, contact_filter): + """Return the group contacts, filtered and sorted""" def compare_contact(a): if not a.get_highest_priority_resource(): return 0 @@ -238,23 +241,17 @@ class RosterGroup(object): if show not in PRESENCE_PRIORITY: return 5 return PRESENCE_PRIORITY[show] - contact_list = self._contacts if not contact_filter\ - else [contact for contact in self._contacts if contact_filter[0](contact, contact_filter[1])] + contact_list = self.contacts if not contact_filter\ + else (contact for contact in self.contacts if contact_filter[0](contact, contact_filter[1])) return sorted(contact_list, key=compare_contact, reverse=True) def toggle_folded(self): self.folded = not self.folded - def __repr__(self): - return '<Roster_group: %s; %s>' % (self.name, self._contacts) - - def __len__(self): - return len(self._contacts) - def get_nb_connected_contacts(self): l = 0 - for contact in self._contacts: - if contact.get_highest_priority_resource(): + for contact in self.contacts: + if contact.resources: l += 1 return l diff --git a/src/tabs.py b/src/tabs.py index 4b39ccc8..df5a9b90 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -633,7 +633,7 @@ class MucTab(ChatTab): compare_users = lambda x: x.last_talked userlist = [user.nick for user in sorted(self.users, key=compare_users, reverse=True)\ if user.nick != self.own_nick] - contact_list = [contact.bare_jid for contact in roster.get_contacts()] + contact_list = [jid for jid in roster.jids()] userlist.extend(contact_list) return the_input.auto_completion(userlist, '', quotify=False) @@ -1788,35 +1788,33 @@ class RosterInfoTab(Tab): """ if not arg: item = self.roster_win.selected_row - if isinstance(item, Contact) and item.ask == 'asked': + if isinstance(item, Contact): jid = item.bare_jid else: self.core.information('No subscription to deny') return else: jid = JID(arg).bare - if not jid in [contact.bare_jid for contact in roster.get_contacts()]: + if not jid in [jid for jid in roster.jids()]: self.core.information('No subscription to deny') return - self.core.xmpp.sendPresence(pto=jid, ptype='unsubscribed') - try: - if self.core.xmpp.update_roster(jid, subscription='remove'): - roster.remove_contact(jid) - except Exception as e: - import traceback - log.debug(_('Traceback when removing %s from the roster:\n' % jid)+traceback.format_exc()) + contact = roster[jid] + if contact: + contact.unauthorize() def command_add(self, args): """ Add the specified JID to the roster, and set automatically accept the reverse subscription """ - jid = JID(args.strip()).bare + jid = JID(JID(args.strip()).bare) if not jid: self.core.information(_('No JID specified'), 'Error') return - self.core.xmpp.sendPresence(pto=jid, ptype='subscribe') + if jid in roster and roster[jid].subscription in ('to', 'both'): + return self.core.information('Already subscribed.', 'Roster') + roster.add(jid) def command_name(self, arg): """ @@ -1828,15 +1826,15 @@ class RosterInfoTab(Tab): jid = JID(args[0]).bare name = args[1] if len(args) == 2 else '' - contact = roster.get_contact_by_jid(jid) - if not contact: + contact = roster[jid] + if contact is None: self.core.information(_('No such JID in roster'), 'Error') return groups = set(contact.groups) subscription = contact.subscription - if self.core.xmpp.update_roster(jid, name=name, groups=groups, subscription=subscription): - contact.name = name + if not self.core.xmpp.update_roster(jid, name=name, groups=groups, subscription=subscription): + self.core.information('The name could not be set.', 'Error') def command_groupadd(self, args): """ @@ -1848,8 +1846,8 @@ class RosterInfoTab(Tab): jid = JID(args[0]).bare group = args[1] - contact = roster.get_contact_by_jid(jid) - if not contact: + contact = roster[jid] + if contact is None: self.core.information(_('No such JID in roster'), 'Error') return @@ -1867,7 +1865,7 @@ class RosterInfoTab(Tab): name = contact.name subscription = contact.subscription if self.core.xmpp.update_roster(jid, name=name, groups=new_groups, subscription=subscription): - roster.edit_groups_of_contact(contact, new_groups) + roster.update_contact_groups(jid) def command_groupmove(self, arg): """ @@ -1880,7 +1878,7 @@ class RosterInfoTab(Tab): group_from = args[1] group_to = args[2] - contact = roster.get_contact_by_jid(jid) + contact = roster[jid.bare] if not contact: self.core.information(_('No such JID in roster'), 'Error') return @@ -1925,8 +1923,8 @@ class RosterInfoTab(Tab): jid = JID(args[0]).bare group = args[1] - contact = roster.get_contact_by_jid(jid) - if not contact: + contact = roster[jid] + if contact is None: self.core.information(_('No such JID in roster'), 'Error') return @@ -1943,7 +1941,7 @@ class RosterInfoTab(Tab): name = contact.name subscription = contact.subscription if self.core.xmpp.update_roster(jid, name=name, groups=new_groups, subscription=subscription): - roster.edit_groups_of_contact(contact, new_groups) + roster.update_contact_groups(jid) def command_remove(self, args): """ @@ -1959,13 +1957,7 @@ class RosterInfoTab(Tab): else: self.core.information('No roster item to remove') return - self.core.xmpp.sendPresence(pto=jid, ptype='unavailable') - self.core.xmpp.sendPresence(pto=jid, ptype='unsubscribe') - self.core.xmpp.sendPresence(pto=jid, ptype='unsubscribed') - try: - self.core.xmpp.del_roster_item(jid=jid) - except: - pass + del roster[jid] def command_import(self, arg): """ @@ -2020,7 +2012,7 @@ class RosterInfoTab(Tab): """ From with any JID presence in the roster """ - jids = [contact.bare_jid for contact in roster.get_contacts()] + jids = [jid for jid in roster.jids()] return the_input.auto_completion(jids, '') def completion_name(self, the_input): @@ -2030,7 +2022,7 @@ class RosterInfoTab(Tab): n += 1 if n == 2: - jids = [contact.bare_jid for contact in roster.get_contacts()] + jids = [jid for jid in roster.jids()] return the_input.auto_completion(jids, '') return False @@ -2041,10 +2033,10 @@ class RosterInfoTab(Tab): n += 1 if n == 2: - jids = [contact.bare_jid for contact in roster.get_contacts()] + jids = [jid for jid in roster.jids()] return the_input.auto_completion(jids, '') elif n == 3: - groups = [group.name for group in roster.get_groups() if group.name != 'none'] + groups = [group for group in roster.groups if group != 'none'] return the_input.auto_completion(groups, '') return False @@ -2056,10 +2048,10 @@ class RosterInfoTab(Tab): n += 1 if n == 2: - jids = [contact.bare_jid for contact in roster.get_contacts()] + jids = [jid for jid in roster.jids()] return the_input.auto_completion(jids, '') elif n == 3: - contact = roster.get_contact_by_jid(args[1]) + contact = roster[args[1]] if not contact: return False groups = list(contact.groups) @@ -2067,7 +2059,7 @@ class RosterInfoTab(Tab): groups.remove('none') return the_input.auto_completion(groups, '') elif n == 4: - groups = [group.name for group in roster.get_groups() if group.name != 'none'] + groups = [group for group in roster.groups] return the_input.auto_completion(groups, '') return False @@ -2079,11 +2071,11 @@ class RosterInfoTab(Tab): n += 1 if n == 2: - jids = [contact.bare_jid for contact in roster.get_contacts()] + jids = [jid for jid in roster.jids()] return the_input.auto_completion(jids, '') elif n == 3: - contact = roster.get_contact_by_jid(args[1]) - if not contact: + contact = roster[args[1]] + if contact is None: return False groups = list(contact.groups) try: @@ -2098,8 +2090,8 @@ class RosterInfoTab(Tab): Complete the first argument from the list of the contact with ask=='subscribe' """ - jids = [contact.bare_jid for contact in roster.get_contacts()\ - if contact.ask == 'asked'] + jids = [str(contact.bare_jid) for contact in roster.contacts.values()\ + if contact.pending_in] return the_input.auto_completion(jids, '', quotify=False) def command_accept(self, arg): @@ -2108,20 +2100,20 @@ class RosterInfoTab(Tab): """ if not arg: item = self.roster_win.selected_row - if isinstance(item, Contact) and item.ask == 'asked': + if isinstance(item, Contact): jid = item.bare_jid else: self.core.information('No subscription to accept') return else: jid = JID(arg).bare - self.core.xmpp.sendPresence(pto=jid, ptype='subscribed') - self.core.xmpp.sendPresence(pto=jid, ptype='') - contact = roster.get_contact_by_jid(jid) - if not contact: + contact = roster[jid] + if contact is None: return - if contact.subscription in ('to', 'none'): - self.core.xmpp.sendPresence(pto=jid, ptype='subscribe') + self.core.xmpp.send_presence(pto=jid, ptype='subscribed') + self.core.xmpp.client_roster.send_last_presence() + if contact.subscription in ('from', 'none') and not contact.pending_out: + self.core.xmpp.send_presence(pto=jid, ptype='subscribe') def refresh(self): if self.need_resize: @@ -2275,8 +2267,8 @@ class RosterInfoTab(Tab): msg = 'Contact: %s (%s)\n%s connected resource%s\nCurrent status: %s' % ( cont.bare_jid, res.presence if res else 'unavailable', - cont.get_nb_resources(), - '' if cont.get_nb_resources() == 1 else 's', + len(cont), + '' if len(cont) == 1 else 's', res.status if res else '',) elif isinstance(selected_row, Resource): res = selected_row @@ -2328,18 +2320,18 @@ class RosterInfoTab(Tab): return True def set_roster_filter_slow(self, txt): - roster._contact_filter = (jid_and_name_match_slow, txt) + roster.jids_filter = (jid_and_name_match_slow, txt) self.roster_win.refresh(roster) return False def set_roster_filter(self, txt): - roster._contact_filter = (jid_and_name_match, txt) + roster.contact_filter = (jid_and_name_match, txt) self.roster_win.refresh(roster) return False def on_search_terminate(self, txt): curses.curs_set(0) - roster._contact_filter = None + roster.contact_filter = None self.reset_help_message() return False @@ -2419,10 +2411,10 @@ class ConversationTab(ChatTab): self.input.refresh() def command_info(self, arg): - contact = roster.get_contact_by_jid(self.get_name()) + contact = roster[self.get_name()] jid = JID(self.get_name()) if jid.resource: - resource = contact.get_resource_by_fulljid(jid.full) + resource = contact[jid.full] else: resource = contact.get_highest_priority_resource() if resource: @@ -2485,14 +2477,15 @@ class ConversationTab(ChatTab): self.resize() log.debug(' TAB Refresh: %s',self.__class__.__name__) self.text_win.refresh() - self.upper_bar.refresh(self.get_name(), roster.get_contact_by_jid(self.get_name())) - self.info_header.refresh(self.get_name(), roster.get_contact_by_jid(self.get_name()), self.text_win, self.chatstate, ConversationTab.additional_informations) + self.upper_bar.refresh(self.get_name(), roster[self.get_name()]) + self.info_header.refresh(self.get_name(), roster[self.get_name()], self.text_win, self.chatstate, ConversationTab.additional_informations) self.info_win.refresh() self.refresh_tab_win() self.input.refresh() def refresh_info_header(self): - self.info_header.refresh(self.get_name(), roster.get_contact_by_jid(self.get_name()), self.text_win, self.chatstate, ConversationTab.additional_informations) + self.info_header.refresh(self.get_name(), roster[self.get_name()] or JID(self.get_name()).user, + self.text_win, self.chatstate, ConversationTab.additional_informations) self.input.refresh() def get_name(self): @@ -2500,7 +2493,7 @@ class ConversationTab(ChatTab): def get_nick(self): jid = JID(self.name) - contact = roster.get_contact_by_jid(jid.bare) + contact = roster[jid.bare] if contact: return contact.name or jid.user else: @@ -2516,11 +2509,11 @@ class ConversationTab(ChatTab): return False def on_lose_focus(self): - contact = roster.get_contact_by_jid(self.get_name()) + contact = roster[self.get_name()] jid = JID(self.get_name()) if contact: if jid.resource: - resource = contact.get_resource_by_fulljid(jid.full) + resource = contact[jid.full] else: resource = contact.get_highest_priority_resource() else: @@ -2533,11 +2526,11 @@ class ConversationTab(ChatTab): self.send_chat_state('inactive') def on_gain_focus(self): - contact = roster.get_contact_by_jid(self.get_name()) + contact = roster[self.get_name()] jid = JID(self.get_name()) if contact: if jid.resource: - resource = contact.get_resource_by_fulljid(jid.full) + resource = contact[jid.full] else: resource = contact.get_highest_priority_resource() else: @@ -2954,6 +2947,8 @@ def jid_and_name_match(contact, txt): return True if txt in JID(contact.bare_jid).user: return True + if txt in contact.name: + return True return False def jid_and_name_match_slow(contact, txt): diff --git a/src/windows.py b/src/windows.py index 802a6230..b0780cf5 100644 --- a/src/windows.py +++ b/src/windows.py @@ -464,7 +464,7 @@ class ConversationInfoWin(InfoWin): jid = JID(jid) if contact: if jid.resource: - resource = contact.get_resource_by_fulljid(jid.full) + resource = contact[jid.full] else: resource = contact.get_highest_priority_resource() else: @@ -539,7 +539,7 @@ class ConversationStatusMessageWin(InfoWin): jid = JID(jid) if contact: if jid.resource: - resource = contact.get_resource_by_fulljid(jid.full) + resource = contact[jid.full] else: resource = contact.get_highest_priority_resource() else: @@ -1544,7 +1544,8 @@ class RosterWin(Win): y = 1 show_offline = config.get('roster_show_offline', 'false') == 'true' for group in roster.get_groups(): - if not show_offline and group.get_nb_connected_contacts() == 0: + contacts_filtered = group.get_contacts(roster.contact_filter) + if (not show_offline and group.get_nb_connected_contacts() == 0) or not contacts_filtered: continue # Ignore empty groups # This loop is really REALLY ugly :^) if y-1 == self.pos: @@ -1554,8 +1555,8 @@ class RosterWin(Win): y += 1 if group.folded: continue - for contact in group.get_contacts(roster._contact_filter): - if not show_offline and contact.get_nb_resources() == 0: + for contact in group.get_contacts(roster.contact_filter): + if not show_offline and len(contact) == 0: continue if y-1 == self.pos: self.selected_row = contact @@ -1564,7 +1565,7 @@ class RosterWin(Win): if y >= self.start_pos: self.draw_contact_line(y-self.start_pos+1, contact, y-1==self.pos) y += 1 - if not contact._folded: + if not contact.folded: for resource in contact.get_resources(): if y-1 == self.pos: self.selected_row = resource @@ -1592,8 +1593,10 @@ class RosterWin(Win): """ The header at the top """ - self.addstr('Roster: %s/%s contacts' % (roster.get_nb_connected_contacts(), roster.get_contact_len())\ - , to_curses_attr(get_theme().COLOR_INFORMATION_BAR)) + self.addstr('Roster: %s/%s contacts' % ( + roster.get_nb_connected_contacts(), + len(roster.contacts)) + ,to_curses_attr(get_theme().COLOR_INFORMATION_BAR)) self.finish_line(get_theme().COLOR_INFORMATION_BAR) def draw_group(self, y, group, colored): @@ -1626,7 +1629,7 @@ class RosterWin(Win): nb = '' else: presence = resource.presence - nb = ' (%s)' % (contact.get_nb_resources(),) + nb = ' (%s)' % len(contact) color = RosterWin.color_show[presence]() if config.getl('show_roster_jids', 'true') == 'false' and contact.name: display_name = '%s %s' % (contact.name, nb[1:]) @@ -1638,7 +1641,7 @@ class RosterWin(Win): self.addstr(y, 0, ' ') self.addstr(get_theme().CHAR_STATUS, to_curses_attr(color)) if resource: - self.addstr(' [+]' if contact._folded else ' [-]') + self.addstr(' [+]' if contact.folded else ' [-]') self.addstr(' ') if colored: self.addstr(display_name, to_curses_attr(get_theme().COLOR_SELECTED_ROW)) @@ -1655,9 +1658,9 @@ class RosterWin(Win): color = RosterWin.color_show[resource.presence]() self.addstr(y, 4, get_theme().CHAR_STATUS, to_curses_attr(color)) if colored: - self.addstr(y, 6, resource.jid.full, to_curses_attr(get_theme().COLOR_SELECTED_ROW)) + self.addstr(y, 6, str(resource.jid), to_curses_attr(get_theme().COLOR_SELECTED_ROW)) else: - self.addstr(y, 6, resource.jid.full) + self.addstr(y, 6, str(resource.jid)) self.finish_line() def get_selected_row(self): @@ -1674,8 +1677,10 @@ class ContactInfoWin(Win): resource = contact.get_highest_priority_resource() if contact: jid = contact.bare_jid + elif resource: + jid = resource.jid else: - jid = jid or resource.jid.full + jid = 'example@example.com' # should never happen if resource: presence = resource.presence else: |