# -*- coding: utf-8 -*-
'''a module to handle contacts'''
# This file is part of emesene.
#
# Emesene is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# emesene is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with emesene; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gettext
import stock
import status
import Object
import validators
_ = gettext.gettext
class ContactManager(Object.Object):
'''this class represent an abstract class that provide methods
to interact with contacts, implementing this class with the
undeliying protocol library let the upper layer use the services
of the protocol implementation independently of the api, in this
way, the protocol library can be modified (or replaced)
without effect on the clients'''
def __init__(self, dialog, protocol, account):
'''initialize the object, dialog is a implementation
ot dialog, it's used to interact with the user'''
Object.Object.__init__(self)
self.dialog = dialog
self.protocol = protocol
self.contacts = {}
# the object that contains out information
self.me = Contact(account)
# emitted when some attribute of the contact changes
# the first argument is the contact object, the second
# is a string representing the attribute that changed
# the third is the old value of the attr
self.signal_add('contact-changed', 3)
# the second parameter is the old value
self.signal_add('contact-nick-changed', 2)
self.signal_add('contact-alias-changed', 2)
self.signal_add('contact-message-changed', 2)
self.signal_add('contact-status-changed', 2)
self.signal_add('contact-picture-changed', 2)
self.signal_add('contact-media-changed', 2)
self.signal_add('contact-blocked-changed', 2)
# contact
self.signal_add('contact-added', 1)
# contact
self.signal_add('contact-removed', 1)
def register(self, contact):
'''this method add the contact to the list of contacts'''
if not self.exists(contact.account):
self.contacts[contact.account] = contact
else:
debug("contact %s already in self.contacts" % (contact.account,))
# utility methods
def exists(self, account):
'''check if the account is on self.contacts, return True if exists'''
if account in self.contacts:
return True
else:
return False
# be carefull and don't redefine this methods!
def _on_contact_added(self, account, identifier=None, nick=None,
message='', _status=status.OFFLINE, alias='', blocked=False):
'''callback called when a new contact is added'''
if account in self.contacts:
debug("contact %s already on contacts" % (account,))
else:
if not nick:
nick = account
contact = Contact(account, identifier, nick, message, _status,
alias, blocked)
self.register(contact)
self.signal_emit('contact-added', contact)
def _on_contact_removed(self, account):
'''callback called when a contact is removed'''
if account in self.contacts:
contact = self.contacts[account]
del self.contacts[account]
self.signal_emit('contact-removed', contact)
else:
debug("contact %s not in contacts" % (account,))
def _on_contact_attr_changed(self, account, attr, value):
'''callback called when an attribute is changed on a contact,
this attribute can be block or alias (since these are the
attributes that the user can change, the others are changed
by the user itself)'''
if not self.exists(account):
debug("contact '%s' not in self.contacts" % (account,))
return
contact = self.contacts[account]
if attr == 'block':
old = contact.blocked
contact._on_blocked_changed(value)
self.signal_emit('contact-blocked-changed', contact, old)
elif attr == 'alias':
old = contact.alias
contact._on_alias_changed(value)
self.signal_emit('contact-alias-changed', contact, old)
else:
old = contact.attrs.get(attr, None)
self.signal_emit('contact-changed', contact, attr, old)
def _on_contact_nick_changed(self, account, nick):
'''callback called when an user change his nick'''
if not self.exists(account):
debug("contact '%s' not in self.contacts" % (account,))
return
contact = self.contacts[account]
old = contact.nick
contact._on_nick_changed(nick)
self.signal_emit('contact-changed', contact, 'nick', old)
self.signal_emit('contact-nick-changed', contact, old)
def _on_contact_message_changed(self, account, message):
'''callback called when an user change his message'''
if not self.exists(account):
debug("contact '%s' not in self.contacts" % (account,))
return
contact = self.contacts[account]
old = contact.message
contact._on_message_changed(message)
self.signal_emit('contact-changed', contact, 'message', old)
self.signal_emit('contact-message-changed', contact, old)
def _on_contact_status_changed(self, account, new_status):
'''callback called when an user change his status'''
if not self.exists(account):
debug("contact '%s' not in self.contacts" % (account,))
return
if status.is_valid(new_status):
contact = self.contacts[account]
old = contact.status
contact._on_status_changed(new_status)
self.signal_emit('contact-changed', contact, 'status', old)
self.signal_emit('contact-status-changed', contact, old)
else:
debug("invalid status '%s'" % (new_status,))
def _on_contact_added_to_group(self, account, group_name):
'''callback called when an account is added to a group'''
if self.exists(account):
if group_name not in self.contacts[account].groups:
self.contacts[account].groups.append(group_name)
else:
debug("group '%s' already in account.groups" % (group_name,))
else:
debug("account %s not in self.contacts" % (account,))
def _on_contact_removed_from_group(self, account, group_name):
'''callback called when an account is removed from a group'''
if self.exists(account):
if group_name in self.contacts[account].groups:
self.contacts[account].groups.remove(group_name)
else:
debug("group '%s' not in account.groups" % (group_name,))
else:
debug("account %s not in self.contacts" % (account,))
# actions on our contact
def get_no_group(self):
'''return a lost of contacts that dont belong to any group'''
return [contact for contact in self.contacts.values() \
if not contact.groups]
def get_contacts(self, accounts):
'''return a list of contact objects from a list of accounts'''
return [self.contacts[account] for account in accounts if account \
in self.contacts]
def get_sorted_list_by_status(self, contacts=None):
'''return a dict with status.* (OFFLINE, ONLINE etc) as key and
a list of contact objects as value, you can use then
status.ORDERED to cycle over the keys.
The contacts are sorted inside the status by display_name.
if contacts is None, then use the internal list of contacts
contacts should be a list of contact objects'''
sorted_dict = {}
contacts = contacts or self.contacts.values()
for stat in status.ORDERED:
sorted_dict[stat] = [contact for contact in contacts \
if contact.status == stat]
sorted_dict[stat].sort(cmp=lambda x, y: cmp(x.display_name,
y.display_name))
return sorted_dict
def get_sorted_list_by_group(self, groups, sort_by_status=False):
'''return a dict with group names as keys and a list of sorted
contacts as value, sort them according to display_name if
sort_by_status is False, and by status and display_name if
it's True'''
groups.sort()
sorted_dict = {}
for group in groups:
contacts = [contact for contact in self.contacts.values() \
if group in contact.groups]
if sort_by_status:
sorted_dict[group] = self.get_sorted_list_by_status(contacts)
else:
contacts.sort(cmp=lambda x, y: cmp(x.display_name,
y.display_name))
sorted_dict[group] = contacts
return sorted_dict
def get_all_contacts(self, contacts=None):
'''return a list of all contacts, this is used to get the contacts
for the exportation of the contact list'''
contacts = contacts or self.contacts.values()
return [contact for contact in contacts]
def get_online_list(self, contacts=None):
'''return a list of non offline contacts'''
contacts = contacts or self.contacts.values()
return [contact for contact in contacts \
if contact.status != status.OFFLINE]
def get_online_total_count(self, contacts):
'''return a tuple with two values, the first is the number of
non offline contacts on the list, the secont is the total number
of contacts'''
total = len(contacts)
online = len([contact for contact in contacts \
if contact.status != status.OFFLINE])
return (online, total)
def set_nick(self, nick):
'''set the nick of our account to nick, dont forget to emit
the contact-nick-changed and contact-changed signals with
self.me as contact'''
pass
def get_nick(self, account=None):
'''return the nick of the account, if account is None, return
our nick'''
if account:
if self.exists(account):
return self.contacts[account].nick
else:
debug("contact '%s' not in self.contacts" % (account,))
return ''
else:
return self.me.nick
def set_message(self, personal_message):
'''set the personal message of our account to personal_message,
dont forget to emit
the contact-message-changed and contact-changed signals with
self.me as contact'''
pass
def get_message(self, account=None):
'''return the personal message if account, an empty string if account
doesn't exist, our message if account=None'''
if account:
if self.exists(account):
return self.contacts[account].message
else:
debug("contact '%s' not in self.contacts" % (account,))
return ''
else:
return self.me.message
def set_media(self, media):
'''set the current media on our account to media
dont forget to emit
the contact-media-changed and contact-changed signals with
self.me as contact'''
pass
def get_media(self, account=None):
'''return the media if account, an empty string if account
doesn't exist, our media if account=None'''
if account:
if self.exists(account):
return self.contacts[account].media
else:
debug("contact '%s' not in self.contacts" % (account,))
return ''
else:
return self.me.media
def set_status(self, new_status):
'''set the status to status, the status should be one of the
constants on status.py, consider calling status.is_valid.
Also you should convert it to the values on the library
dont forget to emit
the contact-status-changed and contact-changed signals with
self.me as contact'''
pass
def get_status(self, account=None):
'''return the status of an account if exist, status.OFFLINE if dont
if account == None, return the status of our user'''
pass
def set_picture(self, path):
'''set the display picture to path'''
pass
def get_picture(self, account=None):
'''return the picture of account, return the picture of the user
if account is None, the type returned is a string with the content
of the image'''
#TODO: implement image cache here
if account:
if self.exists(account):
path = self.contacts[account].picture
else:
debug("contact '%s' not in self.contacts" % (account,))
return None
else:
path = self.me.picture
if validators.readable(path):
return file(path, 'r').read()
else:
return None
# actions on other contacts
def set_alias(self, account, alias):
'''set the contact alias, give an empty alias to reset
dont forget to emit
the contact-alias-changed and contact-changed signals'''
pass
def get_alias(self, account=None):
'''return the alias of the account, if account is None, return
our alias'''
if account:
if self.exists(account):
return self.contacts[account].alias
else:
debug("contact '%s' not in self.contacts" % (account,))
return ''
else:
return self.me.alias
def get_display_name(self, account=None):
'''return the alias or the nick, if account is None, return our
alias or nick'''
if account:
if self.exists(account):
return self.contacts[account].display_name
else:
debug("contact '%s' not in self.contacts" % (account,))
return ''
else:
return self.me.display_name
def block(self, account):
'''block an user
dont forget to emit
the contact-blocked-changed and contact-changed signals'''
pass
def get_blocked(self, account):
'''return True if blocked'''
if self.exists(account):
return self.contacts[account].blocked
else:
debug("contact '%s' not in self.contacts" % (account,))
return False
def unblock(self, account):
'''unblock an user
dont forget to emit
the contact-blocked-changed and contact-changed signals'''
pass
def remove(self, account):
'''remove an user'''
pass
def add(self, account, group=''):
'''add an user'''
pass
def move_to_group(self, account, src_group, dest_group):
'''move a user from src_group to dest_group'''
pass
def add_to_group(self, account, group):
'''add an user to a group, return True on success'''
pass
def remove_from_group(self, account, group):
'''remove an user from a group'''
pass
# dialog (more can be added if needed)
def set_nick_dialog(self):
'''show a dialog asking to change the nick'''
self.dialog.set_nick(self.get_nick(), self.set_nick_cb)
def set_message_dialog(self, old_personal_message):
'''show a dialog asking to change the personal message'''
self.dialog.set_message(old_personal_message,
self.set_message_cb)
def set_alias_dialog(self, account):
'''show the alias dialog'''
self.dialog.set_contact_alias(account, self.get_alias(account),
self.set_alias_cb)
def set_picture_dialog(self, picture_path, picture_dir):
'''show a dialog to select a new picture, picture_path
is the actual picture image path and picture_dir is a
path to a dir where all the pictures to select are located'''
self.dialog.set_picture(picture_path, picture_dir,
self._set_picture_cb)
def remove_dialog(self, account):
'''show a confirmation dialog to ask if sure to remove the
user, it's optional to use, but recomended'''
self.dialog.yes_no(
_("Are you sure you want to delete %s?") % (account, ),
self.remove_cb, account)
def add_dialog(self):
'''show a dialog to ask for the account, and if the account
is valid, add the user'''
self.dialog.add_contact(self.add_cb)
# callbacks, (the extra parameters are set as '' by default because
# when the user close the window pressing the x then this parameters
# are not sent
def set_nick_cb(self, response, old_nick='', new_nick=''):
'''callback for the dialog.set_nick method'''
if response == stock.ACCEPT:
if old_nick == new_nick:
debug('old nick and new nick are the same')
return
elif new_nick == '':
debug('empty new nick')
return
self.set_nick(new_nick)
def set_message_cb(self, response, old_pm='', new_pm=''):
'''callback for the dialog.set_message method'''
if response == stock.ACCEPT:
if old_pm == new_pm:
debug('old and new personal messages are the same')
return
self.set_message(new_pm)
def set_alias_cb(self, response, account='', old_alias='', new_alias=''):
'''callback for the dialog.set_contact_alias method,
the parameters and the values are described on that method'''
if response == stock.ACCEPT:
if old_alias == new_alias:
debug('old alias and new alias are the same')
return
self.set_alias(account, new_alias)
elif response == stock.CLEAR:
self.set_alias(account, '')
def remove_cb(self, response, account=''):
'''callback for dialog.yes_no, asking to confirm the
user remove'''
if response == stock.YES:
self.remove(account)
def add_cb(self, response, account='', groups=''):
'''callback to the add_dialog method, add the user and add him
to the defined groups'''
if response == stock.ADD:
self.add(account)
if groups:
for group in groups:
self.add_to_group(account, group)
def _set_picture_cb(self, response, path):
'''callback to the set_picture_dialog, change the picture of
self.me'''
if response == stock.ACCEPT:
self.set_picture(path)
elif response == stock.CLEAR:
self.set_picture('')
class Contact(object):
'''a class that represent a contact'''
def __init__(self, account, identifier=None, nick='', message=None,
_status=status.OFFLINE, alias='', blocked=False):
'''class contructor'''
self.account = account
self.identifier = identifier or '0'
self.nick = nick or self.account
# message is the personal message or status message, as you may
# call it
self.message = message or ''
self.media = ''
self.status = _status
self.alias = alias
self.blocked = blocked
self.picture = None
self.groups = []
# extra atributes (use contact.attrs.get("attr", "default"))
self.attrs = {}
def _on_nick_changed(self, nick):
'''callback called on nick change'''
self.nick = nick
def _on_message_changed(self, message):
'''callback called on message change'''
self.message = message
def _on_status_changed(self, _status):
'''callback called on status change'''
self.status = _status
def _on_alias_changed(self, alias):
'''callback called on alias change'''
self.alias = alias
def _on_blocked_changed(self, blocked):
'''callback called on blocked change'''
self.blocked = blocked
def _get_display_name(self):
'''return the alias if exist, if not the nick if not empty, if not
the mail'''
return self.alias or self.nick or self.account
display_name = property(fget=_get_display_name)
def _get_status_string(self):
'''return a string representation of the status'''
return status.STATUS.get(self.status, '?')
status_string = property(fget=_get_status_string)
def __repr__(self):
'''return a string representation of the object'''
return "<contact account='%s'>" % (self.account,)
def debug(msg):
'''debug method, the module send the debug here, it can be changed
to use another debugging method'''
return
print('ContactManager.py: ', msg)
|