Multi-resource. Handling <presence /> stanzas. But the normal conversation...

Multi-resource. Handling <presence /> stanzas. But the normal conversation window is broken :^).  Fixed #1888
parent 17ebb92e
......@@ -16,31 +16,16 @@
from sleekxmpp.xmlstream.jid import JID
class Contact(object):
class Resource(object):
"""
Defines a roster item
Defines a roster item.
It's a precise resource.
"""
def __init__(self, jid):
self._jid = JID(jid) # a SleekXMPP jid object
self._display_name = None
self._subscription = 'none'
self._ask = None
self._jid = JID(jid) # Full jid
self._status = ''
self._presence = 'unavailable'
self._priority = 0
self._groups = [] # a list of groups the contact is in
def set_ask(self, ask):
self._ask = ask
def get_ask(self):
return self._ask
def set_subscription(self, sub):
self._subscription = sub
def get_subscription(self, sub):
return self._subscription
def get_jid(self):
return self._jid
......@@ -52,6 +37,9 @@ class Contact(object):
assert isinstance(priority, int)
self._priority = priority
def get_priority(self):
return self._priority
def set_presence(self, pres):
self._presence = pres
......@@ -64,8 +52,109 @@ class Contact(object):
def set__status(self, s):
self._status = s
class Contact(object):
"""
This a way to gather multiple resources from the same bare JID.
This class contains zero or more esource class 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 get_bare_jid(self):
"""
Just get the bare_jid or the contact
"""
return self._jid
def get_highest_priority_resource(self):
"""
There must be, at any time, at least ONE resource.
And they always should be ordered by priority.
"""
ret = None
for resource in self._resources:
if not ret or ret.get_priority() < resource.get_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)
"""
# TODO sort by priority
self._resources.append(resource)
def remove_resource(self, resource):
"""
Called, for example, when one resource goes offline.
"""
self._resources.remove(resource)
def remove_resource_by_fulljid(self, fulljid):
"""
Like 'remove_resource' but just by knowing the full jid
"""
for resource in self._resources:
if resource.get_jid().full == fulljid:
self._resources.remove(resource)
return
assert False
def get_resource_by_fulljid(self, fulljid):
"""
Return the resource with the given fulljid
"""
for resource in self._resources:
if resource.get_jid().full == fulljid:
return resource
return None
def toggle_folded(self):
"""
Fold if it's unfolded, and vice versa
"""
self._folded = not self._folded
def set_name(self, name):
self._display_name = name
def get_name(self):
return self._display_name
def set_ask(self, ask):
self._ask = ask
def get_ask(self):
return self._ask
def set_subscription(self, sub):
self._subscription = sub
def get_subscription(self, sub):
return self._subscription
def get_nb_resources(self):
"""
Get the number of connected resources
"""
return len(self._resources)
def get_resources(self):
"""
Return all resources
"""
compare_resources = lambda x: x.get_priority()
return sorted(self._resources, key=compare_resources)
def __repr__(self):
ret = '<Contact: %s' % self._jid
for resource in self._resources:
ret += '\n\t\t%s'%resource
return ret + ' />\n'
......@@ -40,13 +40,12 @@ from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab, ConversationTab
from user import User
from room import Room
from roster import Roster, RosterGroup
from contact import Contact
from contact import Contact, Resource
from message import Message
from text_buffer import TextBuffer
from keyboard import read_char
from common import jid_get_domain, is_jid
from common import debug
# http://xmpp.org/extensions/xep-0045.html#errorstatus
ERROR_AND_STATUS_CODES = {
'401': _('A password is required'),
......@@ -148,7 +147,7 @@ class Gui(object):
self.xmpp.add_event_handler("got_online" , self.on_got_online)
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("presence", self.on_presence)
self.xmpp.add_event_handler("changed_status", self.on_presence)
def grow_information_win(self):
"""
......@@ -175,19 +174,28 @@ class Gui(object):
contact = self.roster.get_contact_by_jid(jid.bare)
if not contact:
return
contact.set_presence('unavailable')
self.information('%s is offline' % (contact.get_jid()), "Roster")
resource = contact.get_resource_by_fulljid(jid.full)
assert resource
self.information('%s is offline' % (resource.get_jid()), "Roster")
contact.remove_resource(resource)
if isinstance(self.current_tab(), RosterInfoTab):
self.refresh_window()
def on_got_online(self, presence):
jid = presence['from']
contact = self.roster.get_contact_by_jid(jid.bare)
if not contact:
# Todo, handle presence comming from contacts not in roster
return
resource = contact.get_resource_by_fulljid(jid.full)
assert not resource
resource = Resource(jid.full)
status = presence['type']
priority = presence.getPriority()
contact.set_presence(status)
contact.set_priority(priority)
self.information("%s is online (%s)" % (contact.get_jid(), status), "Roster")
priority = presence.getPriority() or 0
resource.set_presence(status)
resource.set_priority(priority)
contact.add_resource(resource)
self.information("%s is online (%s)" % (resource.get_jid().full, status), "Roster")
def on_connected(self, event):
"""
......@@ -445,8 +453,6 @@ class Gui(object):
"""
When receiving "normal" messages (from someone in our roster)
"""
from common import debug
debug('MESSAGE: %s\n' % (message))
jid = message['from'].bare
room = self.get_conversation_by_jid(jid)
body = message['body']
......@@ -463,9 +469,20 @@ class Gui(object):
def on_presence(self, presence):
"""
"""
from common import debug
debug('Presence: %s\n' % (presence))
return
jid = presence['from']
# contact = ros
contact = self.roster.get_contact_by_jid(jid.bare)
if not contact:
return
resource = contact.get_resource_by_fulljid(jid.full)
if not resource:
return
status = presence['type']
priority = presence.getPriority() or 0
resource.set_presence(status)
resource.set_priority(priority)
if isinstance(self.current_tab(), RosterInfoTab):
self.refresh_window()
def on_roster_update(self, iq):
"""
......@@ -491,7 +508,6 @@ class Gui(object):
groups = item.findall('{jabber:iq:roster}group')
self.roster.edit_groups_of_contact(contact, [group.text for group in groups])
if isinstance(self.current_tab(), RosterInfoTab):
# TODO refresh roster_win only
self.refresh_window()
def call_for_resize(self):
......@@ -1297,12 +1313,16 @@ class Gui(object):
when enter is pressed on the roster window
"""
if isinstance(roster_row, Contact):
if not self.get_conversation_by_jid(roster_row.get_jid().bare):
self.open_conversation_window(roster_row.get_jid().bare)
# roster_row.toggle_folded()
if not self.get_conversation_by_jid(roster_row.get_bare_jid()):
self.open_conversation_window(roster_row.get_bare_jid())
else:
self.focus_tab_named(roster_row.get_bare_jid())
if isinstance(roster_row, Resource):
if not self.get_conversation_by_jid(roster_row.get_jid().full):
self.open_conversation_window(roster_row.get_jid().full)
else:
self.focus_tab_named(roster_row.get_jid().bare)
elif isinstance(roster_row, RosterGroup):
roster_row.folded = not roster_row.folded
self.focus_tab_named(roster_row.get_jid().full)
self.refresh_window()
def execute(self,line):
......
......@@ -14,13 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
from contact import Contact
from common import debug
from contact import Contact, Resource
class Roster(object):
def __init__(self):
self._contacts = {} # key = jid; value = Contact()
self._contacts = {} # key = bare jid; value = Contact()
self._roster_groups = []
def add_contact(self, contact, jid):
......@@ -97,6 +95,8 @@ class Roster(object):
if not group.folded:
for contact in group.get_contacts():
l += 1
if not contact._folded:
l += contact.get_nb_resources()
return l
def __repr__(self):
......@@ -115,10 +115,10 @@ class RosterGroup(object):
Online/Offline or whatever
"""
def __init__(self, name, folded=False):
# debug('New group: %s \n' % name)
self._contacts = []
self.name = name
self.folded = folded # if the group content is to be shown
def is_empty(self):
return len(self._contacts) == 0
......@@ -143,3 +143,6 @@ class RosterGroup(object):
def __repr__(self):
return '<Roster_group: %s; %s>' % (self.name, self._contacts)
def toggle_folded(self):
self.folded = not self.folded
......@@ -29,8 +29,7 @@ import window
import theme
import curses
from roster import RosterGroup
from common import debug
from contact import Contact, Resource
class Tab(object):
"""
......@@ -426,7 +425,10 @@ class RosterInfoTab(Tab):
def on_input(self, key):
if key in ('\n', '^J', '^M') and self.input.is_empty():
return self.on_enter()
return self.input.do_command(key)
if key == ' ':
return self.on_space()
# In writting mode
# return self.input.do_command(key)
def on_lose_focus(self):
self._color_state = theme.COLOR_TAB_NORMAL
......@@ -447,6 +449,12 @@ class RosterInfoTab(Tab):
def on_info_win_size_changed(self, _, __):
pass
def on_space(self):
selected_row = self.roster_win.get_selected_row()
if isinstance(selected_row, RosterGroup) or\
isinstance(selected_row, Contact):
selected_row.toggle_folded()
return True
def on_enter(self):
selected_row = self.roster_win.get_selected_row()
return selected_row
......@@ -478,7 +486,7 @@ class ConversationTab(Tab):
def refresh(self, tabs, informations, roster):
self.text_win.refresh(self._room)
self.info_header.refresh(self._room, roster.get_contact_by_jid(self._room.name))
# self.info_header.refresh(self._room, roster.get_contact_by_jid(self._room.name))
self.info_win.refresh(informations)
self.tab_win.refresh(tabs, tabs[0])
self.input.refresh()
......
......@@ -27,7 +27,7 @@ from config import config
from threading import Lock
from contact import Contact
from contact import Contact, Resource
from roster import RosterGroup
from message import Line
......@@ -35,8 +35,6 @@ from tab import MIN_WIDTH, MIN_HEIGHT
import theme
from common import debug
g_lock = Lock()
class Win(object):
......@@ -255,11 +253,13 @@ class ConversationInfoWin(InfoWin):
# contact can be None, if we receive a message
# from someone not in our roster. In this case, we display
# only the maximum information from the message we can get.
# Also, contact can be a resource, if we're talking to a
# specific resource.
with g_lock:
self._win.erase()
self.write_room_name(contact, room)
self.print_scroll_position(room)
self.finish_line(theme.COLOR_INFORMATION_BAR)
# self.write_room_name(resource, room)
# self.print_scroll_position(room)
# self.finish_line(theme.COLOR_INFORMATION_BAR)
self._refresh()
def write_room_name(self, contact, room):
......@@ -926,7 +926,7 @@ class RosterWin(Win):
'dnd':theme.COLOR_STATUS_DND,
'away':theme.COLOR_STATUS_AWAY,
'chat':theme.COLOR_STATUS_CHAT,
'unavailable': theme.COLOR_STATUS_UNAVAILABLE
'unavailable':theme.COLOR_STATUS_UNAVAILABLE
}
# subscription_char = {'both': '
def __init__(self, height, width, y, x, parent_win, visible):
......@@ -971,6 +971,7 @@ class RosterWin(Win):
self.draw_roster_information(roster)
y = 1
for group in roster.get_groups():
# This loop is really REALLY ugly :^)
if y-1 == self.pos:
self.selected_row = group
if y >= self.start_pos:
......@@ -986,6 +987,15 @@ 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:
for resource in contact.get_resources():
if y-1 == self.pos:
self.selected_row = resource
if y-self.start_pos+1 == self.height:
break
if y >= self.start_pos:
self.draw_resource_line(y-self.start_pos+1, resource, y-1==self.pos)
y += 1
if y-self.start_pos+1 == self.height:
break
if self.start_pos > 1:
......@@ -1023,21 +1033,46 @@ class RosterWin(Win):
def draw_contact_line(self, y, contact, colored):
"""
Draw on a line all informations about one contact
Draw on a line all informations about one contact.
This is basically the highest priority resource's informations
Use 'color' to draw the jid/display_name to show what is
is currently selected contact in the list
"""
color = RosterWin.color_show[contact.get_presence()]
the currently selected contact in the list
"""
resource = contact.get_highest_priority_resource()
if not resource:
# There's no online resource
presence = 'unavailable'
folder = ' '
nb = ''
else:
presence = resource.get_presence()
folder = '[+]' if contact._folded else '[-]'
nb = '(%s)' % (contact.get_nb_resources(),)
color = RosterWin.color_show[presence]
if contact.get_name():
display_name = '%s (%s)' % (contact.get_name(),
contact.get_jid().bare)
display_name = '%s (%s) %s' % (contact.get_name(),
contact.get_bare_jid(), nb,)
else:
display_name = '%s' % (contact.get_jid().bare,)
display_name = '%s %s' % (contact.get_bare_jid(), nb,)
self.addstr(y, 1, " ", curses.color_pair(color))
if resource:
self.addstr(y, 2, ' [+]' if contact._folded else ' [-]')
self.addstr(' ')
if colored:
self.addstr(y, 4, display_name, curses.color_pair(14))
self.addstr(display_name, curses.color_pair(14))
else:
self.addstr(y, 4, display_name)
self.addstr(display_name)
def draw_resource_line(self, y, resource, colored):
"""
Draw a specific resource line
"""
color = RosterWin.color_show[resource.get_presence()]
self.addstr(y, 4, " ", curses.color_pair(color))
if colored:
self.addstr(y, 6, resource.get_jid().full, curses.color_pair(14))
else:
self.addstr(y, 6, resource.get_jid().full)
def get_selected_row(self):
return self.selected_row
......@@ -1051,12 +1086,17 @@ class ContactInfoWin(Win):
self._resize(height, width, y, x, stdscr, visible)
self.visible = visible
def draw_contact_info(self, contact):
def draw_contact_info(self, resource, jid=None):
"""
draw the contact information
"""
self.addstr(0, 0, contact.get_jid().full, curses.color_pair(theme.COLOR_INFORMATION_BAR))
self.addstr(' (%s)'%(contact.get_presence(),), curses.color_pair(theme.COLOR_INFORMATION_BAR))
jid = jid or resource.get_jid().full
if resource:
presence = resource.get_presence()
else:
presence = 'unavailable'
self.addstr(0, 0, jid, curses.color_pair(theme.COLOR_INFORMATION_BAR))
self.addstr(' (%s)'%(presence,), curses.color_pair(theme.COLOR_INFORMATION_BAR))
self.finish_line(theme.COLOR_INFORMATION_BAR)
def draw_group_info(self, group):
......@@ -1074,5 +1114,8 @@ class ContactInfoWin(Win):
if isinstance(selected_row, RosterGroup):
self.draw_group_info(selected_row)
elif isinstance(selected_row, Contact):
self.draw_contact_info(selected_row.get_highest_priority_resource(),
selected_row.get_bare_jid())
elif isinstance(selected_row, Resource):
self.draw_contact_info(selected_row)
self._refresh()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment