poezio/src/roster.py
mathieui 1c1ab3cb83
Merge branch 'master' of git.poez.io:poezio into slix
Conflicts:
	src/bookmark.py
	src/config.py
	src/connection.py
	src/core/commands.py
	src/core/core.py
	src/core/handlers.py
	src/windows/info_bar.py
	src/windows/muc.py
	src/windows/roster_win.py
	src/windows/text_win.py
	src/xhtml.py
2014-10-31 19:16:44 +01:00

330 lines
10 KiB
Python

# 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 = {}
# 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):
if not jid in self.contacts:
contact = Contact(self.__node[jid])
self.contacts[jid] = contact
return contact
return self.contacts[jid]
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)
return l
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
"""
n = 0
for contact in self:
if self.exists(contact) and len(contact):
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.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 len(self.jids())
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