handle nested roster group. TODO: Improve the way it's displayed in roster. Fixes...
[gajim.git] / src / common / contacts.py
blobe115ef46d8383a1900bca2876e99d50914ef12fb
1 # -*- coding:utf-8 -*-
2 ## src/common/contacts.py
3 ##
4 ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
5 ## Travis Shirk <travis AT pobox.com>
6 ## Nikos Kouremenos <kourem AT gmail.com>
7 ## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org>
8 ## Jean-Marie Traissard <jim AT lapin.org>
9 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
10 ## Tomasz Melcer <liori AT exroot.org>
11 ## Julien Pivotto <roidelapluie AT gmail.com>
12 ## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
13 ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
14 ## Jonathan Schleifer <js-gajim AT webkeks.org>
16 ## This file is part of Gajim.
18 ## Gajim is free software; you can redistribute it and/or modify
19 ## it under the terms of the GNU General Public License as published
20 ## by the Free Software Foundation; version 3 only.
22 ## Gajim is distributed in the hope that it will be useful,
23 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
24 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 ## GNU General Public License for more details.
27 ## You should have received a copy of the GNU General Public License
28 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
32 from common import caps_cache
33 from common.account import Account
34 import common.gajim
36 class XMPPEntity(object):
37 """
38 Base representation of entities in XMPP
39 """
41 def __init__(self, jid, account, resource):
42 self.jid = jid
43 self.resource = resource
44 self.account = account
46 class CommonContact(XMPPEntity):
48 def __init__(self, jid, account, resource, show, status, name,
49 our_chatstate, composing_xep, chatstate, client_caps=None):
51 XMPPEntity.__init__(self, jid, account, resource)
53 self.show = show
54 self.status = status
55 self.name = name
57 self.client_caps = client_caps or caps_cache.NullClientCaps()
59 # please read xep-85 http://www.xmpp.org/extensions/xep-0085.html
60 # we keep track of xep85 support with the peer by three extra states:
61 # None, False and 'ask'
62 # None if no info about peer
63 # False if peer does not support xep85
64 # 'ask' if we sent the first 'active' chatstate and are waiting for reply
65 # this holds what WE SEND to contact (our current chatstate)
66 self.our_chatstate = our_chatstate
67 # tell which XEP we're using for composing state
68 # None = have to ask, XEP-0022 = use this xep,
69 # XEP-0085 = use this xep, False = no composing support
70 self.composing_xep = composing_xep
71 # this is contact's chatstate
72 self.chatstate = chatstate
74 def get_full_jid(self):
75 raise NotImplementedError
77 def get_shown_name(self):
78 raise NotImplementedError
80 def supports(self, requested_feature):
81 """
82 Return True if the contact has advertised to support the feature
83 identified by the given namespace. False otherwise.
84 """
85 if self.show == 'offline':
86 # Unfortunately, if all resources are offline, the contact
87 # includes the last resource that was online. Check for its
88 # show, so we can be sure it's existant. Otherwise, we still
89 # return caps for a contact that has no resources left.
90 return False
91 else:
92 return caps_cache.client_supports(self.client_caps, requested_feature)
95 class Contact(CommonContact):
96 """
97 Information concerning a contact
98 """
99 def __init__(self, jid, account, name='', groups=[], show='', status='',
100 sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
101 our_chatstate=None, chatstate=None, last_status_time=None, msg_id=
102 None, composing_xep=None, last_activity_time=None):
104 CommonContact.__init__(self, jid, account, resource, show, status, name,
105 our_chatstate, composing_xep, chatstate, client_caps=client_caps)
107 self.contact_name = '' # nick choosen by contact
108 self.groups = [i for i in set(groups)] # filter duplicate values
110 self.sub = sub
111 self.ask = ask
113 self.priority = priority
114 self.keyID = keyID
115 self.msg_id = msg_id
116 self.last_status_time = last_status_time
117 self.last_activity_time = last_activity_time
119 self.pep = {}
121 def get_full_jid(self):
122 if self.resource:
123 return self.jid + '/' + self.resource
124 return self.jid
126 def get_shown_name(self):
127 if self.name:
128 return self.name
129 if self.contact_name:
130 return self.contact_name
131 return self.jid.split('@')[0]
133 def get_shown_groups(self):
134 if self.is_observer():
135 return [_('Observers')]
136 elif self.is_groupchat():
137 return [_('Groupchats')]
138 elif self.is_transport():
139 return [_('Transports')]
140 elif not self.groups:
141 return [_('General')]
142 else:
143 return self.groups
145 def is_hidden_from_roster(self):
147 If contact should not be visible in roster
149 # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
150 if self.is_transport():
151 return False
152 if self.sub in ('both', 'to'):
153 return False
154 if self.sub in ('none', 'from') and self.ask == 'subscribe':
155 return False
156 if self.sub in ('none', 'from') and (self.name or len(self.groups)):
157 return False
158 if _('Not in Roster') in self.groups:
159 return False
160 return True
162 def is_observer(self):
163 # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
164 is_observer = False
165 if self.sub == 'from' and not self.is_transport()\
166 and self.is_hidden_from_roster():
167 is_observer = True
168 return is_observer
170 def is_groupchat(self):
171 for account in common.gajim.gc_connected:
172 if self.jid in common.gajim.gc_connected[account]:
173 return True
174 return False
176 def is_transport(self):
177 # if not '@' or '@' starts the jid then contact is transport
178 return self.jid.find('@') <= 0
181 class GC_Contact(CommonContact):
183 Information concerning each groupchat contact
186 def __init__(self, room_jid, account, name='', show='', status='', role='',
187 affiliation='', jid='', resource='', our_chatstate=None,
188 composing_xep=None, chatstate=None):
190 CommonContact.__init__(self, jid, account, resource, show, status, name,
191 our_chatstate, composing_xep, chatstate)
193 self.room_jid = room_jid
194 self.role = role
195 self.affiliation = affiliation
197 def get_full_jid(self):
198 return self.room_jid + '/' + self.name
200 def get_shown_name(self):
201 return self.name
203 def as_contact(self):
205 Create a Contact instance from this GC_Contact instance
207 return Contact(jid=self.get_full_jid(), account=self.account,
208 name=self.name, groups=[], show=self.show, status=self.status,
209 sub='none', client_caps=self.client_caps)
212 class LegacyContactsAPI:
214 This is a GOD class for accessing contact and groupchat information.
215 The API has several flaws:
217 * it mixes concerns because it deals with contacts, groupchats,
218 groupchat contacts and metacontacts
219 * some methods like get_contact() may return None. This leads to
220 a lot of duplication all over Gajim because it is not sure
221 if we receive a proper contact or just None.
223 It is a long way to cleanup this API. Therefore just stick with it
224 and use it as before. We will try to figure out a migration path.
226 def __init__(self):
227 self._metacontact_manager = MetacontactManager(self)
228 self._accounts = {}
230 def change_account_name(self, old_name, new_name):
231 self._accounts[new_name] = self._accounts[old_name]
232 self._accounts[new_name].name = new_name
233 del self._accounts[old_name]
235 self._metacontact_manager.change_account_name(old_name, new_name)
237 def add_account(self, account_name):
238 self._accounts[account_name] = Account(account_name, Contacts(),
239 GC_Contacts())
240 self._metacontact_manager.add_account(account_name)
242 def get_accounts(self):
243 return self._accounts.keys()
245 def remove_account(self, account):
246 del self._accounts[account]
247 self._metacontact_manager.remove_account(account)
249 def create_contact(self, jid, account, name='', groups=[], show='',
250 status='', sub='', ask='', resource='', priority=0, keyID='',
251 client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None,
252 composing_xep=None, last_activity_time=None):
253 # Use Account object if available
254 account = self._accounts.get(account, account)
255 return Contact(jid=jid, account=account, name=name, groups=groups,
256 show=show, status=status, sub=sub, ask=ask, resource=resource,
257 priority=priority, keyID=keyID, client_caps=client_caps,
258 our_chatstate=our_chatstate, chatstate=chatstate,
259 last_status_time=last_status_time, composing_xep=composing_xep,
260 last_activity_time=last_activity_time)
262 def create_self_contact(self, jid, account, resource, show, status, priority,
263 name='', keyID=''):
264 conn = common.gajim.connections[account]
265 nick = name or common.gajim.nicks[account]
266 account = self._accounts.get(account, account) # Use Account object if available
267 self_contact = self.create_contact(jid=jid, account=account,
268 name=nick, groups=['self_contact'], show=show, status=status,
269 sub='both', ask='none', priority=priority, keyID=keyID,
270 resource=resource)
271 self_contact.pep = conn.pep
272 return self_contact
274 def create_not_in_roster_contact(self, jid, account, resource='', name='', keyID=''):
275 account = self._accounts.get(account, account) # Use Account object if available
276 return self.create_contact(jid=jid, account=account, resource=resource,
277 name=name, groups=[_('Not in Roster')], show='not in roster',
278 status='', sub='none', keyID=keyID)
280 def copy_contact(self, contact):
281 return self.create_contact(contact.jid, contact.account,
282 name=contact.name, groups=contact.groups, show=contact.show,
283 status=contact.status, sub=contact.sub, ask=contact.ask,
284 resource=contact.resource, priority=contact.priority,
285 keyID=contact.keyID, client_caps=contact.client_caps,
286 our_chatstate=contact.our_chatstate, chatstate=contact.chatstate,
287 last_status_time=contact.last_status_time,
288 composing_xep=contact.composing_xep,
289 last_activity_time=contact.last_activity_time)
291 def add_contact(self, account, contact):
292 if account not in self._accounts:
293 self.add_account(account)
294 return self._accounts[account].contacts.add_contact(contact)
296 def remove_contact(self, account, contact):
297 if account not in self._accounts:
298 return
299 return self._accounts[account].contacts.remove_contact(contact)
301 def remove_jid(self, account, jid, remove_meta=True):
302 self._accounts[account].contacts.remove_jid(jid)
303 if remove_meta:
304 self._metacontact_manager.remove_metacontact(account, jid)
306 def get_contacts(self, account, jid):
307 return self._accounts[account].contacts.get_contacts(jid)
309 def get_contact(self, account, jid, resource=None):
310 return self._accounts[account].contacts.get_contact(jid, resource=resource)
312 def iter_contacts(self, account):
313 for contact in self._accounts[account].contacts.iter_contacts():
314 yield contact
316 def get_contact_from_full_jid(self, account, fjid):
317 return self._accounts[account].contacts.get_contact_from_full_jid(fjid)
319 def get_first_contact_from_jid(self, account, jid):
320 return self._accounts[account].contacts.get_first_contact_from_jid(jid)
322 def get_contacts_from_group(self, account, group):
323 return self._accounts[account].contacts.get_contacts_from_group(group)
325 def get_contacts_jid_list(self, account):
326 return self._accounts[account].contacts.get_contacts_jid_list()
328 def get_jid_list(self, account):
329 return self._accounts[account].contacts.get_jid_list()
331 def change_contact_jid(self, old_jid, new_jid, account):
332 return self._accounts[account].change_contact_jid(old_jid, new_jid)
334 def get_highest_prio_contact_from_contacts(self, contacts):
335 if not contacts:
336 return None
337 prim_contact = contacts[0]
338 for contact in contacts[1:]:
339 if int(contact.priority) > int(prim_contact.priority):
340 prim_contact = contact
341 return prim_contact
343 def get_contact_with_highest_priority(self, account, jid):
344 contacts = self.get_contacts(account, jid)
345 if not contacts and '/' in jid:
346 # jid may be a fake jid, try it
347 room, nick = jid.split('/', 1)
348 contact = self.get_gc_contact(account, room, nick)
349 return contact
350 return self.get_highest_prio_contact_from_contacts(contacts)
352 def get_nb_online_total_contacts(self, accounts=[], groups=[]):
354 Return the number of online contacts and the total number of contacts
356 if accounts == []:
357 accounts = self.get_accounts()
358 nbr_online = 0
359 nbr_total = 0
360 for account in accounts:
361 our_jid = common.gajim.get_jid_from_account(account)
362 for jid in self.get_jid_list(account):
363 if jid == our_jid:
364 continue
365 if common.gajim.jid_is_transport(jid) and not \
366 _('Transports') in groups:
367 # do not count transports
368 continue
369 if self.has_brother(account, jid, accounts) and not \
370 self.is_big_brother(account, jid, accounts):
371 # count metacontacts only once
372 continue
373 contact = self.get_contact_with_highest_priority(account, jid)
374 if _('Not in roster') in contact.groups:
375 continue
376 in_groups = False
377 if groups == []:
378 in_groups = True
379 else:
380 for group in groups:
381 if group in contact.get_shown_groups():
382 in_groups = True
383 break
385 if in_groups:
386 if contact.show not in ('offline', 'error'):
387 nbr_online += 1
388 nbr_total += 1
389 return nbr_online, nbr_total
391 def __getattr__(self, attr_name):
392 # Only called if self has no attr_name
393 if hasattr(self._metacontact_manager, attr_name):
394 return getattr(self._metacontact_manager, attr_name)
395 else:
396 raise AttributeError(attr_name)
398 def create_gc_contact(self, room_jid, account, name='', show='', status='',
399 role='', affiliation='', jid='', resource=''):
400 account = self._accounts.get(account, account) # Use Account object if available
401 return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid,
402 resource)
404 def add_gc_contact(self, account, gc_contact):
405 return self._accounts[account].gc_contacts.add_gc_contact(gc_contact)
407 def remove_gc_contact(self, account, gc_contact):
408 return self._accounts[account].gc_contacts.remove_gc_contact(gc_contact)
410 def remove_room(self, account, room_jid):
411 return self._accounts[account].gc_contacts.remove_room(room_jid)
413 def get_gc_list(self, account):
414 return self._accounts[account].gc_contacts.get_gc_list()
416 def get_nick_list(self, account, room_jid):
417 return self._accounts[account].gc_contacts.get_nick_list(room_jid)
419 def get_gc_contact(self, account, room_jid, nick):
420 return self._accounts[account].gc_contacts.get_gc_contact(room_jid, nick)
422 def get_nb_role_total_gc_contacts(self, account, room_jid, role):
423 return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role)
426 class Contacts():
428 This is a breakout of the contact related behavior of the old
429 Contacts class (which is not called LegacyContactsAPI)
431 def __init__(self):
432 # list of contacts {jid1: [C1, C2]}, } one Contact per resource
433 self._contacts = {}
435 def add_contact(self, contact):
436 if contact.jid not in self._contacts:
437 self._contacts[contact.jid] = [contact]
438 return
439 contacts = self._contacts[contact.jid]
440 # We had only one that was offline, remove it
441 if len(contacts) == 1 and contacts[0].show == 'offline':
442 # Do not use self.remove_contact: it deteles
443 # self._contacts[account][contact.jid]
444 contacts.remove(contacts[0])
445 # If same JID with same resource already exists, use the new one
446 for c in contacts:
447 if c.resource == contact.resource:
448 self.remove_contact(c)
449 break
450 contacts.append(contact)
452 def remove_contact(self, contact):
453 if contact.jid not in self._contacts:
454 return
455 if contact in self._contacts[contact.jid]:
456 self._contacts[contact.jid].remove(contact)
457 if len(self._contacts[contact.jid]) == 0:
458 del self._contacts[contact.jid]
460 def remove_jid(self, jid):
462 Remove all contacts for a given jid
464 if jid in self._contacts:
465 del self._contacts[jid]
467 def get_contacts(self, jid):
469 Return the list of contact instances for this jid
471 return self._contacts.get(jid, [])
473 def get_contact(self, jid, resource=None):
474 ### WARNING ###
475 # This function returns a *RANDOM* resource if resource = None!
476 # Do *NOT* use if you need to get the contact to which you
477 # send a message for example, as a bare JID in Jabber means
478 # highest available resource, which this function ignores!
480 Return the contact instance for the given resource if it's given else the
481 first contact is no resource is given or None if there is not
483 if jid in self._contacts:
484 if not resource:
485 return self._contacts[jid][0]
486 for c in self._contacts[jid]:
487 if c.resource == resource:
488 return c
490 def iter_contacts(self):
491 for jid in self._contacts.keys():
492 for contact in self._contacts[jid][:]:
493 yield contact
495 def get_jid_list(self):
496 return self._contacts.keys()
498 def get_contacts_jid_list(self):
499 return [jid for jid, contact in self._contacts.iteritems() if not
500 contact[0].is_groupchat()]
502 def get_contact_from_full_jid(self, fjid):
504 Get Contact object for specific resource of given jid
506 barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid)
507 return self.get_contact(barejid, resource)
509 def get_first_contact_from_jid(self, jid):
510 if jid in self._contacts:
511 return self._contacts[jid][0]
513 def get_contacts_from_group(self, group):
515 Return all contacts in the given group
517 group_contacts = []
518 for jid in self._contacts:
519 contacts = self.get_contacts(jid)
520 if group in contacts[0].groups:
521 group_contacts += contacts
522 return group_contacts
524 def change_contact_jid(self, old_jid, new_jid):
525 if old_jid not in self._contacts:
526 return
527 self._contacts[new_jid] = []
528 for _contact in self._contacts[old_jid]:
529 _contact.jid = new_jid
530 self._contacts[new_jid].append(_contact)
531 del self._contacts[old_jid]
534 class GC_Contacts():
536 def __init__(self):
537 # list of contacts that are in gc {room_jid: {nick: C}}}
538 self._rooms = {}
540 def add_gc_contact(self, gc_contact):
541 if gc_contact.room_jid not in self._rooms:
542 self._rooms[gc_contact.room_jid] = {gc_contact.name: gc_contact}
543 else:
544 self._rooms[gc_contact.room_jid][gc_contact.name] = gc_contact
546 def remove_gc_contact(self, gc_contact):
547 if gc_contact.room_jid not in self._rooms:
548 return
549 if gc_contact.name not in self._rooms[gc_contact.room_jid]:
550 return
551 del self._rooms[gc_contact.room_jid][gc_contact.name]
552 # It was the last nick in room ?
553 if not len(self._rooms[gc_contact.room_jid]):
554 del self._rooms[gc_contact.room_jid]
556 def remove_room(self, room_jid):
557 if room_jid in self._rooms:
558 del self._rooms[room_jid]
560 def get_gc_list(self):
561 return self._rooms.keys()
563 def get_nick_list(self, room_jid):
564 gc_list = self.get_gc_list()
565 if not room_jid in gc_list:
566 return []
567 return self._rooms[room_jid].keys()
569 def get_gc_contact(self, room_jid, nick):
570 nick_list = self.get_nick_list(room_jid)
571 if not nick in nick_list:
572 return None
573 return self._rooms[room_jid][nick]
575 def get_nb_role_total_gc_contacts(self, room_jid, role):
577 Return the number of group chat contacts for the given role and the total
578 number of group chat contacts
580 if room_jid not in self._rooms:
581 return 0, 0
582 nb_role = nb_total = 0
583 for nick in self._rooms[room_jid]:
584 if self._rooms[room_jid][nick].role == role:
585 nb_role += 1
586 nb_total += 1
587 return nb_role, nb_total
590 class MetacontactManager():
592 def __init__(self, contacts):
593 self._metacontacts_tags = {}
594 self._contacts = contacts
596 def change_account_name(self, old_name, new_name):
597 self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name]
598 del self._metacontacts_tags[old_name]
600 def add_account(self, account):
601 if account not in self._metacontacts_tags:
602 self._metacontacts_tags[account] = {}
604 def remove_account(self, account):
605 del self._metacontacts_tags[account]
607 def define_metacontacts(self, account, tags_list):
608 self._metacontacts_tags[account] = tags_list
610 def _get_new_metacontacts_tag(self, jid):
611 if not jid in self._metacontacts_tags:
612 return jid
613 #FIXME: can this append ?
614 assert False
616 def iter_metacontacts_families(self, account):
617 for tag in self._metacontacts_tags[account]:
618 family = self._get_metacontacts_family_from_tag(account, tag)
619 yield family
621 def _get_metacontacts_tag(self, account, jid):
623 Return the tag of a jid
625 if not account in self._metacontacts_tags:
626 return None
627 for tag in self._metacontacts_tags[account]:
628 for data in self._metacontacts_tags[account][tag]:
629 if data['jid'] == jid:
630 return tag
631 return None
633 def add_metacontact(self, brother_account, brother_jid, account, jid, order=None):
634 tag = self._get_metacontacts_tag(brother_account, brother_jid)
635 if not tag:
636 tag = self._get_new_metacontacts_tag(brother_jid)
637 self._metacontacts_tags[brother_account][tag] = [{'jid': brother_jid,
638 'tag': tag}]
639 if brother_account != account:
640 common.gajim.connections[brother_account].store_metacontacts(
641 self._metacontacts_tags[brother_account])
642 # be sure jid has no other tag
643 old_tag = self._get_metacontacts_tag(account, jid)
644 while old_tag:
645 self.remove_metacontact(account, jid)
646 old_tag = self._get_metacontacts_tag(account, jid)
647 if tag not in self._metacontacts_tags[account]:
648 self._metacontacts_tags[account][tag] = [{'jid': jid, 'tag': tag}]
649 else:
650 if order:
651 self._metacontacts_tags[account][tag].append({'jid': jid,
652 'tag': tag, 'order': order})
653 else:
654 self._metacontacts_tags[account][tag].append({'jid': jid,
655 'tag': tag})
656 common.gajim.connections[account].store_metacontacts(
657 self._metacontacts_tags[account])
659 def remove_metacontact(self, account, jid):
660 if not account in self._metacontacts_tags:
661 return
663 found = None
664 for tag in self._metacontacts_tags[account]:
665 for data in self._metacontacts_tags[account][tag]:
666 if data['jid'] == jid:
667 found = data
668 break
669 if found:
670 self._metacontacts_tags[account][tag].remove(found)
671 common.gajim.connections[account].store_metacontacts(
672 self._metacontacts_tags[account])
673 break
675 def has_brother(self, account, jid, accounts):
676 tag = self._get_metacontacts_tag(account, jid)
677 if not tag:
678 return False
679 meta_jids = self._get_metacontacts_jids(tag, accounts)
680 return len(meta_jids) > 1 or len(meta_jids[account]) > 1
682 def is_big_brother(self, account, jid, accounts):
683 family = self.get_metacontacts_family(account, jid)
684 if family:
685 nearby_family = [data for data in family
686 if account in accounts]
687 bb_data = self._get_metacontacts_big_brother(nearby_family)
688 if bb_data['jid'] == jid and bb_data['account'] == account:
689 return True
690 return False
692 def _get_metacontacts_jids(self, tag, accounts):
694 Return all jid for the given tag in the form {acct: [jid1, jid2],.}
696 answers = {}
697 for account in self._metacontacts_tags:
698 if tag in self._metacontacts_tags[account]:
699 if account not in accounts:
700 continue
701 answers[account] = []
702 for data in self._metacontacts_tags[account][tag]:
703 answers[account].append(data['jid'])
704 return answers
706 def get_metacontacts_family(self, account, jid):
708 Return the family of the given jid, including jid in the form:
709 [{'account': acct, 'jid': jid, 'order': order}, ] 'order' is optional
711 tag = self._get_metacontacts_tag(account, jid)
712 return self._get_metacontacts_family_from_tag(account, tag)
714 def _get_metacontacts_family_from_tag(self, account, tag):
715 if not tag:
716 return []
717 answers = []
718 for account in self._metacontacts_tags:
719 if tag in self._metacontacts_tags[account]:
720 for data in self._metacontacts_tags[account][tag]:
721 data['account'] = account
722 answers.append(data)
723 return answers
725 def _compare_metacontacts(self, data1, data2):
727 Compare 2 metacontacts
729 Data is {'jid': jid, 'account': account, 'order': order} order is
730 optional
732 jid1 = data1['jid']
733 jid2 = data2['jid']
734 account1 = data1['account']
735 account2 = data2['account']
736 contact1 = self._contacts.get_contact_with_highest_priority(account1, jid1)
737 contact2 = self._contacts.get_contact_with_highest_priority(account2, jid2)
738 show_list = ['not in roster', 'error', 'offline', 'invisible', 'dnd',
739 'xa', 'away', 'chat', 'online', 'requested', 'message']
740 # contact can be null when a jid listed in the metacontact data
741 # is not in our roster
742 if not contact1:
743 if contact2:
744 return -1 # prefer the known contact
745 else:
746 show1 = 0
747 priority1 = 0
748 else:
749 show1 = show_list.index(contact1.show)
750 priority1 = contact1.priority
751 if not contact2:
752 if contact1:
753 return 1 # prefer the known contact
754 else:
755 show2 = 0
756 priority2 = 0
757 else:
758 show2 = show_list.index(contact2.show)
759 priority2 = contact2.priority
760 # If only one is offline, it's always second
761 if show1 > 2 and show2 < 3:
762 return 1
763 if show2 > 2 and show1 < 3:
764 return -1
765 if 'order' in data1 and 'order' in data2:
766 if data1['order'] > data2['order']:
767 return 1
768 if data1['order'] < data2['order']:
769 return -1
770 if 'order' in data1:
771 return 1
772 if 'order' in data2:
773 return -1
774 transport1 = common.gajim.get_transport_name_from_jid(jid1)
775 transport2 = common.gajim.get_transport_name_from_jid(jid2)
776 if transport2 and not transport1:
777 return 1
778 if transport1 and not transport2:
779 return -1
780 if show1 > show2:
781 return 1
782 if show2 > show1:
783 return -1
784 if priority1 > priority2:
785 return 1
786 if priority2 > priority1:
787 return -1
788 server1 = common.gajim.get_server_from_jid(jid1)
789 server2 = common.gajim.get_server_from_jid(jid2)
790 myserver1 = common.gajim.config.get_per('accounts', account1, 'hostname')
791 myserver2 = common.gajim.config.get_per('accounts', account2, 'hostname')
792 if server1 == myserver1:
793 if server2 != myserver2:
794 return 1
795 elif server2 == myserver2:
796 return -1
797 if jid1 > jid2:
798 return 1
799 if jid2 > jid1:
800 return -1
801 # If all is the same, compare accounts, they can't be the same
802 if account1 > account2:
803 return 1
804 if account2 > account1:
805 return -1
806 return 0
808 def get_nearby_family_and_big_brother(self, family, account):
810 Return the nearby family and its Big Brother
812 Nearby family is the part of the family that is grouped with the
813 metacontact. A metacontact may be over different accounts. If accounts
814 are not merged then the given family is split account wise.
816 (nearby_family, big_brother_jid, big_brother_account)
818 if common.gajim.config.get('mergeaccounts'):
819 # group all together
820 nearby_family = family
821 else:
822 # we want one nearby_family per account
823 nearby_family = [data for data in family if account == data['account']]
825 big_brother_data = self._get_metacontacts_big_brother(nearby_family)
826 big_brother_jid = big_brother_data['jid']
827 big_brother_account = big_brother_data['account']
829 return (nearby_family, big_brother_jid, big_brother_account)
831 def _get_metacontacts_big_brother(self, family):
833 Which of the family will be the big brother under wich all others will be
836 family.sort(cmp=self._compare_metacontacts)
837 return family[-1]