diff options
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> | 2016-03-31 18:54:41 +0100 |
---|---|---|
committer | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> | 2016-06-11 20:49:43 +0100 |
commit | 332a5c2553db41de777473a1e1be9cd1522c9496 (patch) | |
tree | 3ee06a59f147ccc4009b35cccfbe2461bcd18310 /poezio/roster.py | |
parent | cf44cf7cdec9fdb35caa372563d57e7045dc29dd (diff) | |
download | poezio-332a5c2553db41de777473a1e1be9cd1522c9496.tar.gz poezio-332a5c2553db41de777473a1e1be9cd1522c9496.tar.bz2 poezio-332a5c2553db41de777473a1e1be9cd1522c9496.tar.xz poezio-332a5c2553db41de777473a1e1be9cd1522c9496.zip |
Move the src directory to poezio, for better cython compatibility.
Diffstat (limited to 'poezio/roster.py')
-rw-r--r-- | poezio/roster.py | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/poezio/roster.py b/poezio/roster.py new file mode 100644 index 00000000..ba7da63e --- /dev/null +++ b/poezio/roster.py @@ -0,0 +1,334 @@ +# Copyright 2010-2011 Florent Le Coz <louiz@louiz.org> +# +# This file is part of Poezio. +# +# Poezio is free software: you can redistribute it and/or modify +# it under the terms of the zlib license. See the COPYING file. + + +""" +Defines the Roster and RosterGroup classes +""" +import logging +log = logging.getLogger(__name__) + +from config import config +from contact import Contact +from roster_sorting import SORTING_METHODS, GROUP_SORTING_METHODS + +from os import path as p +from datetime import datetime +from common import safeJID +from slixmpp.exceptions import IqError, IqTimeout + + +class Roster(object): + """ + The proxy class to get the roster from slixmpp. + Caches Contact and RosterGroup objects. + """ + + def __init__(self): + """ + node: the RosterSingle from slixmpp + """ + self.__node = None + self.contact_filter = None # A tuple(function, *args) + # function to filter contacts, + # on search, for example + self.folded_groups = set(config.get('folded_roster_groups', + section='var').split(':')) + self.groups = {} + self.contacts = {} + self.length = 0 + self.connected = 0 + + # Used for caching roster infos + self.last_built = datetime.now() + self.last_modified = datetime.now() + + def modified(self): + self.last_modified = datetime.now() + + @property + def needs_rebuild(self): + return self.last_modified >= self.last_built + + def __getitem__(self, key): + """Get a Contact from his bare JID""" + key = safeJID(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 __setitem__(self, key, value): + """Set the a Contact value for the bare jid key""" + self.contacts[key] = value + + def remove(self, jid): + """Send a removal iq to the server""" + jid = safeJID(jid).bare + if self.__node[jid]: + try: + self.__node[jid].send_presence(ptype='unavailable') + self.__node.remove(jid) + except (IqError, IqTimeout): + log.debug('IqError when removing %s:', jid, exc_info=True) + + def __delitem__(self, jid): + """Remove a contact from the roster view""" + jid = safeJID(jid).bare + contact = self[jid] + if not contact: + return + del self.contacts[contact.bare_jid] + + for group in list(self.groups.values()): + group.remove(contact) + if not group: + del self.groups[group.name] + self.modified() + + 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 safeJID(key).bare in self.jids() + + @property + def jid(self): + """Our JID""" + return self.__node.jid + + def get_and_set(self, jid): + contact = self.contacts.get(jid) + if contact is None: + contact = Contact(self.__node[jid]) + self.contacts[jid] = contact + return contact + return contact + + def set_node(self, value): + """Set the slixmpp RosterSingle for our roster""" + self.__node = value + + def get_groups(self, sort=''): + """Return a list of the RosterGroups""" + group_list = sorted( + (group for group in self.groups.values() if group), + key=lambda x: x.name.lower() if x.name else '' + ) + + for sorting in sort.split(':'): + if sorting == 'reverse': + group_list = list(reversed(group_list)) + else: + method = GROUP_SORTING_METHODS.get(sorting, lambda x: 0) + group_list = sorted(group_list, key=method) + return group_list + + 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""" + l = [] + for key in self.__node.keys(): + contact = self.get_and_set(key) + if key != self.jid and (contact and self.exists(contact)): + l.append(key) + self.update_size(l) + return l + + def update_size(self, jids=None): + if jids is None: + jids = self.jids() + self.length = len(jids) + + def get_contacts(self): + """ + Return a list of all the contacts + """ + return [self[jid] for jid in self.jids()] + + def get_contacts_sorted_filtered(self, sort=''): + """ + Return a list of all the contacts sorted with a criteria + """ + contact_list = [] + for contact in self.get_contacts(): + if contact.bare_jid != self.jid: + if self.contact_filter: + if self.contact_filter[0](contact, self.contact_filter[1]): + contact_list.append(contact) + else: + contact_list.append(contact) + contact_list = sorted(contact_list, key=SORTING_METHODS['name']) + + for sorting in sort.split(':'): + if sorting == 'reverse': + contact_list = list(reversed(contact_list)) + else: + method = SORTING_METHODS.get(sorting, lambda x: 0) + contact_list = sorted(contact_list, key=method) + return contact_list + + 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.groups.values()\ + if group.folded]) + log.debug('folded:%s\n', folded_groups) + return config.silent_set('folded_roster_groups', folded_groups, 'var') + + def get_nb_connected_contacts(self): + """ + Get the number of connected contacts + """ + return self.connected + + def update_contact_groups(self, contact): + """Regenerate the RosterGroups when receiving a contact update""" + if not isinstance(contact, Contact): + contact = self.get_and_set(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): + """ + Return the number of contacts + (used to return the display size, but now we have + the display cache in RosterWin for that) + """ + return self.length + + def __repr__(self): + ret = '== Roster:\nContacts:\n' + for contact in self.contacts.values(): + ret += '%s\n' % (contact,) + ret += 'Groups\n' + 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+', encoding='utf-8') + f.writelines([str(i) + "\n" for i in self.contacts if self[i] and (self[i].subscription == "both" or self[i].ask)]) + f.close() + return True + except IOError: + return + except OSError: + return + + def exists(self, contact): + if not contact: + return False + for group in contact.groups: + if contact not in self.groups.get(group, tuple()): + return False + return True + + +class RosterGroup(object): + """ + A RosterGroup is a group containing contacts + It can be Friends/Family etc, but also can be + Online/Offline or whatever + """ + def __init__(self, name, contacts=None, folded=False): + if not contacts: + contacts = [] + self.contacts = set(contacts) + self.name = name if name is not None else '' + self.folded = folded # if the group content is to be shown + + def __iter__(self): + """Iterate over the contacts""" + return iter(self.contacts) + + def __repr__(self): + return '<Roster_group: %s; %s>' % (self.name, self.contacts) + + def __len__(self): + """Number of contacts in the group""" + return len(self.contacts) + + def __contains__(self, contact): + """ + Return a bool, telling if the contact is in the group + """ + 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=None, sort=''): + """Return the group contacts, filtered and sorted""" + contact_list = self.contacts.copy() if not contact_filter\ + else [contact for contact in self.contacts.copy() if contact_filter[0](contact, contact_filter[1])] + contact_list = sorted(contact_list, key=SORTING_METHODS['name']) + + for sorting in sort.split(':'): + if sorting == 'reverse': + contact_list = list(reversed(contact_list)) + else: + method = SORTING_METHODS.get(sorting, lambda x: 0) + contact_list = sorted(contact_list, key=method) + return contact_list + + def toggle_folded(self): + """Fold/unfold the group in the roster""" + self.folded = not self.folded + if self.folded: + if self.name not in roster.folded_groups: + roster.folded_groups.add(self.name) + else: + if self.name in roster.folded_groups: + roster.folded_groups.remove(self.name) + + def get_nb_connected_contacts(self): + """Return the number of connected contacts""" + return len([1 for contact in self.contacts if len(contact)]) + +def create_roster(): + "Create the global roster object" + global roster + roster = Roster() + +# Shared roster object +roster = None |