summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/contact.py196
-rw-r--r--src/core.py187
-rw-r--r--src/roster.py297
-rw-r--r--src/tabs.py123
-rw-r--r--src/windows.py31
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: