diff --git a/src/chat_control.py b/src/chat_control.py index 7b62ec6cd..7d80a410e 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -577,7 +577,7 @@ class ChatControlBase(MessageControl): type_ = 'printed_' + self.type_id event = 'message_received' show_in_roster = notify.get_show_in_roster(event, - self.account, self.contact) + self.account, self.contact, self.session) show_in_systray = notify.get_show_in_systray(event, self.account, self.contact) if gc_message: @@ -606,7 +606,7 @@ class ChatControlBase(MessageControl): not self.parent_win.is_active() or not end) and \ kind in ('incoming', 'incoming_queue'): self.parent_win.redraw_tab(self) - ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, self.account) + ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, self.account, self.session.thread_id) if not self.parent_win.is_active(): self.parent_win.show_title(True, ctrl) # Enabled Urgent hint else: @@ -905,10 +905,10 @@ class ChatControl(ChatControlBase): old_msg_kind = None # last kind of the printed message CHAT_CMDS = ['clear', 'compact', 'help', 'ping'] - def __init__(self, parent_win, contact, acct, resource = None): + def __init__(self, parent_win, contact, acct, session, resource = None): ChatControlBase.__init__(self, self.TYPE_ID, parent_win, 'chat_child_vbox', contact, acct, resource) - + # for muc use: # widget = self.xml.get_widget('muc_window_actions_button') widget = self.xml.get_widget('message_window_actions_button') @@ -935,7 +935,7 @@ class ChatControl(ChatControlBase): # it is on enter-notify and leave-notify so no need to be per jid self.show_bigger_avatar_timeout_id = None self.bigger_avatar_window = None - self.show_avatar(self.contact.resource) + self.show_avatar(self.contact.resource) # chatstate timers and state self.reset_kbd_mouse_timeout_vars() @@ -949,7 +949,7 @@ class ChatControl(ChatControlBase): id = message_tv_buffer.connect('changed', self._on_message_tv_buffer_changed) self.handlers[id] = message_tv_buffer - + widget = self.xml.get_widget('avatar_eventbox') id = widget.connect('enter-notify-event', self.on_avatar_eventbox_enter_notify_event) @@ -969,7 +969,9 @@ class ChatControl(ChatControlBase): if self.contact.jid in gajim.encrypted_chats[self.account]: self.xml.get_widget('gpg_togglebutton').set_active(True) - + + self.session = session + self.status_tooltip = gtk.Tooltips() self.update_ui() # restore previous conversation @@ -1797,7 +1799,7 @@ class ChatControl(ChatControlBase): # Is it a pm ? is_pm = False room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) - control = gajim.interface.msg_win_mgr.get_control(room_jid, self.account) + control = gajim.interface.msg_win_mgr.get_control(room_jid, self.account, self.session.thread_id) if control and control.type_id == message_control.TYPE_GC: is_pm = True # list of message ids which should be marked as read @@ -1815,9 +1817,6 @@ class ChatControl(ChatControlBase): encrypted = data[4], subject = data[1], xhtml = data[7]) if len(data) > 6 and isinstance(data[6], int): message_ids.append(data[6]) - - if len(data) > 8: - self.set_thread_id(data[8]) if message_ids: gajim.logger.set_read_messages(message_ids) gajim.events.remove_events(self.account, jid_with_resource, diff --git a/src/common/connection.py b/src/common/connection.py index a8460ba27..6deee84a6 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -48,6 +48,8 @@ log = logging.getLogger('gajim.c.connection') import gtkgui_helpers +import time + class Connection(ConnectionHandlers): '''Connection class''' def __init__(self, name): @@ -839,7 +841,7 @@ class Connection(ConnectionHandlers): def send_message(self, jid, msg, keyID, type = 'chat', subject='', chatstate = None, msg_id = None, composing_jep = None, resource = None, - user_nick = None, xhtml = None, thread = None): + user_nick = None, xhtml = None, session = None): if not self.connection: return 1 if msg and not xhtml and gajim.config.get('rst_formatting_outgoing_messages'): @@ -884,8 +886,10 @@ class Connection(ConnectionHandlers): msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc) # XEP-0201 - if thread: - msg_iq.setTag("thread").setData(thread) + if session: + session.last_send = time.time() + if session.thread_id: + msg_iq.setThread(session.thread_id) # JEP-0172: user_nickname if user_nick: diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index fa392b71b..346497f5c 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -37,6 +37,8 @@ from common import atom from common.commands import ConnectionCommands from common.pubsub import ConnectionPubSub +from common.stanza_session import StanzaSession + STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'error'] # kind of events we can wait for an answer @@ -1171,6 +1173,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, # keep the latest subscribed event for each jid to prevent loop when we # acknoledge presences self.subscribed_events = {} + + # keep track of sessions this connection has with other JIDs + self.sessions = {} + try: idle.init() except: @@ -1197,13 +1203,13 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, self.dispatch('HTTP_AUTH', (method, url, id, iq_obj, msg)); raise common.xmpp.NodeProcessed - def _FeatureNegCB(self, con, stanza): + def _FeatureNegCB(self, con, stanza, session): gajim.log.debug('FeatureNegCB') feature = stanza.getTag('feature') form = common.xmpp.DataForm(node=feature.getTag('x')) if form['FORM_TYPE'] == 'urn:xmpp:ssn': - self.dispatch('SESSION_NEG', (stanza.getFrom(), stanza.getThread(), form)) + self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form)) else: reply = stanza.buildReply() reply.setType('error') @@ -1410,6 +1416,17 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, def _messageCB(self, con, msg): '''Called when we receive a message''' + frm = helpers.get_full_jid_from_iq(msg) + mtype = msg.getType() + thread_id = msg.getThread() + if not mtype: + mtype = 'normal' + + session = self.get_session(frm, thread_id, mtype) + + if thread_id and not session.received_thread_id: + session.received_thread_id = True + # check if the message is pubsub#event if msg.getTag('event') is not None: self._pubsubEventCB(con, msg) @@ -1421,18 +1438,15 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, return if msg.getTag('feature') and msg.getTag('feature').namespace == \ common.xmpp.NS_FEATURE: - self._FeatureNegCB(con, msg) + self._FeatureNegCB(con, msg, session) return msgtxt = msg.getBody() msghtml = msg.getXHTML() - mtype = msg.getType() subject = msg.getSubject() # if not there, it's None - thread = msg.getThread() tim = msg.getTimestamp() tim = time.strptime(tim, '%Y%m%dT%H:%M:%S') tim = time.localtime(timegm(tim)) - frm = helpers.get_full_jid_from_iq(msg) jid = helpers.get_jid_from_iq(msg) no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for') @@ -1541,13 +1555,42 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim, subject = subject) mtype = 'normal' + treat_as = gajim.config.get('treat_incoming_messages') if treat_as: mtype = treat_as self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, - subject, chatstate, msg_id, composing_jep, user_nick, msghtml, thread)) + subject, chatstate, msg_id, composing_jep, user_nick, msghtml, session)) # END messageCB + def get_session(self, jid, thread_id, type): + '''returns an existing session between this connection and 'jid' or starts a new one.''' + try: + if type == 'chat' and not thread_id: + return self.find_null_session(jid) + else: + return self.sessions[jid][thread_id] + except KeyError: + return self.make_new_session(jid, thread_id, type) + + def find_null_session(self, jid): + '''returns the session between this connecting and 'jid' that we last sent a message in.''' + all = self.sessions[jid].values() + null_sessions = filter(lambda s: not s.received_thread_id, all) + null_sessions.sort(key=lambda s: s.last_send) + + return null_sessions[-1] + + def make_new_session(self, jid, thread_id = None, type = 'chat'): + sess = StanzaSession(self, jid, thread_id, type) + + if not jid in self.sessions: + self.sessions[jid] = {} + + self.sessions[jid][sess.thread_id] = sess + + return sess + def _pubsubEventCB(self, con, msg): ''' Called when we receive with pubsub event. ''' # TODO: Logging? (actually services where logging would be useful, should diff --git a/src/common/contacts.py b/src/common/contacts.py index d6352cea6..68db5b766 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -16,7 +16,7 @@ import common.gajim -import random, string +#import random, string class Contact: '''Information concerning each contact''' @@ -53,18 +53,18 @@ class Contact: self.last_status_time = last_status_time # XEP-0201 - self.sessions = {} +# self.sessions = {} - def new_session(self): - thread_id = "".join([random.choice(string.letters) for x in xrange(0,32)]) - self.sessions[self.get_full_jid()] = thread_id - return thread_id +# def new_session(self): +# thread_id = "".join([random.choice(string.letters) for x in xrange(0,32)]) +# self.sessions[self.get_full_jid()] = thread_id +# return thread_id - def get_session(self): - try: - return self.sessions[self.get_full_jid()] - except KeyError: - return None +# def get_session(self): +# try: +# return self.sessions[self.get_full_jid()] +# except KeyError: +# return None def get_full_jid(self): if self.resource: @@ -169,7 +169,7 @@ class Contacts: return Contact(jid, name, groups, show, status, sub, ask, resource, priority, keyID, our_chatstate, chatstate, last_status_time, composing_jep) - + def copy_contact(self, contact): return self.create_contact(jid = contact.jid, name = contact.name, groups = contact.groups, show = contact.show, status = contact.status, diff --git a/src/common/helpers.py b/src/common/helpers.py index 12388bf81..60252e6bf 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -820,7 +820,7 @@ def allow_sound_notification(sound_event, advanced_notif_num = None): return True return False -def get_chat_control(account, contact): +def get_chat_control(account, contact, session): full_jid_with_resource = contact.jid if contact.resource: full_jid_with_resource += '/' + contact.resource @@ -829,16 +829,16 @@ def get_chat_control(account, contact): # Look for a chat control that has the given resource, or default to # one without resource ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource, - account) + account, session.thread_id) if ctrl: return ctrl elif not highest_contact or not highest_contact.resource: # unknow contact or offline message - return gajim.interface.msg_win_mgr.get_control(contact.jid, account) + return gajim.interface.msg_win_mgr.get_control(contact.jid, account, session.thread_id) elif highest_contact and contact.resource != \ highest_contact.resource: return None - return gajim.interface.msg_win_mgr.get_control(contact.jid, account) + return gajim.interface.msg_win_mgr.get_control(contact.jid, account, session.thread_id) def reduce_chars_newlines(text, max_chars = 0, max_lines = 0): '''Cut the chars after 'max_chars' on each line diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py new file mode 100644 index 000000000..0e3c3b7ec --- /dev/null +++ b/src/common/stanza_session.py @@ -0,0 +1,51 @@ +import gajim + +from common import xmpp +from common import helpers + +import random +import string + +class StanzaSession: + def __init__(self, conn, jid, thread_id, type): + self.conn = conn + + if isinstance(jid, str) or isinstance(jid, unicode): + self.jid = xmpp.JID(jid) + else: + self.jid = jid + + self.type = type + + if thread_id: + self.received_thread_id = True + self.thread_id = thread_id + else: + self.received_thread_id = False + if type == 'normal': + self.thread_id = None + else: + self.thread_id = self.generate_thread_id() + + self.last_send = 0 + + def generate_thread_id(self): + return "".join([random.choice(string.letters) for x in xrange(0,32)]) + + def get_control(self, advanced_notif_num = None): + account = self.conn.name + highest_contact = gajim.contacts.get_contact_with_highest_priority(account, str(self.jid)) + contact = gajim.contacts.get_contact(account, self.jid.getStripped(), self.jid.getResource()) + if isinstance(contact, list): + # there was no resource (maybe we're reading unread messages after shutdown). just take the first one for now :/ + contact = contact[0] + + ctrl = gajim.interface.msg_win_mgr.get_control(str(self.jid), account, self.thread_id) +# if not ctrl: +# if highest_contact and contact.resource == highest_contact.resource and not str(self.jid) == gajim.get_jid_from_account(account): +# ctrl = gajim.interface.msg_win_mgr.get_control(self.jid.getStripped(), account, self.thread_id) + + if not ctrl and helpers.allow_popup_window(account, advanced_notif_num): + gajim.new_chat(contact, account, resource = resource_for_chat, session = self) + + return ctrl diff --git a/src/dialogs.py b/src/dialogs.py index 2d0409676..526e6b95b 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -1404,7 +1404,7 @@ class SynchroniseSelectAccountDialog: if not iter: return remote_account = model.get_value(iter, 0).decode('utf-8') - + if gajim.connections[remote_account].connected < 2: ErrorDialog(_('This account is not connected to the server'), _('You cannot synchronize with an account unless it is connected.')) @@ -1686,7 +1686,7 @@ class SingleMessageWindow: or 'receive'. ''' def __init__(self, account, to = '', action = '', from_whom = '', - subject = '', message = '', resource = '', thread = None): + subject = '', message = '', resource = '', session = None): self.account = account self.action = action @@ -1695,7 +1695,7 @@ class SingleMessageWindow: self.to = to self.from_whom = from_whom self.resource = resource - self.thread = thread + self.session = session self.xml = gtkgui_helpers.get_glade('single_message_window.glade') self.window = self.xml.get_widget('single_message_window') @@ -1897,7 +1897,7 @@ class SingleMessageWindow: # FIXME: allow GPG message some day gajim.connections[self.account].send_message(to_whom_jid, message, - keyID = None, type = 'normal', subject=subject, thread = self.thread) + keyID = None, type = 'normal', subject=subject, session = self.session) self.subject_entry.set_text('') # we sent ok, clear the subject self.message_tv_buffer.set_text('') # we sent ok, clear the textview @@ -1914,7 +1914,7 @@ class SingleMessageWindow: self.window.destroy() SingleMessageWindow(self.account, to = self.from_whom, action = 'send', from_whom = self.from_whom, subject = self.subject, - message = self.message, thread = self.thread) + message = self.message, session = self.session) def on_send_and_close_button_clicked(self, widget): self.send_single_message() @@ -2099,7 +2099,7 @@ class PrivacyListWindow: jid_entry_completion.set_text_column(0) jid_entry_completion.set_model(jids_list_store) jid_entry_completion.set_popup_completion(True) - self.edit_type_jabberid_entry.set_completion(jid_entry_completion) + self.edit_type_jabberid_entry.set_completion(jid_entry_completion) if action == 'EDIT': self.refresh_rules() diff --git a/src/gajim.py b/src/gajim.py index 53ab20863..5c13131e1 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -443,9 +443,9 @@ class Interface: (jid_from, file_props)) conn.disconnect_transfer(file_props) return - ctrl = self.msg_win_mgr.get_control(jid_from, account) - if ctrl and ctrl.type_id == message_control.TYPE_GC: - ctrl.print_conversation('Error %s: %s' % (array[2], array[1])) + for ctrl in self.msg_win_mgr.get_controls(jid=jid_from, acct=account): + if ctrl and ctrl.type_id == message_control.TYPE_GC: + ctrl.print_conversation('Error %s: %s' % (array[2], array[1])) def handle_event_con_type(self, account, con_type): # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'tcp' @@ -667,7 +667,7 @@ class Interface: def handle_event_msg(self, account, array): # 'MSG' (account, (jid, msg, time, encrypted, msg_type, subject, - # chatstate, msg_id, composing_jep, user_nick, xhtml, thread)) + # chatstate, msg_id, composing_jep, user_nick, xhtml, session)) # user_nick is JEP-0172 full_jid_with_resource = array[0] @@ -682,13 +682,13 @@ class Interface: msg_id = array[7] composing_jep = array[8] xhtml = array[10] - thread = array[11] + session = array[11] if gajim.config.get('ignore_incoming_xhtml'): xhtml = None if gajim.jid_is_transport(jid): jid = jid.replace('@', '') - groupchat_control = self.msg_win_mgr.get_control(jid, account) + groupchat_control = self.msg_win_mgr.get_control(jid, account, session.thread_id) if not groupchat_control and \ gajim.interface.minimized_controls.has_key(account) and \ jid in gajim.interface.minimized_controls[account]: @@ -700,28 +700,29 @@ class Interface: pm = True msg_type = 'pm' - chat_control = None - jid_of_control = full_jid_with_resource +# chat_control = None +# jid_of_control = full_jid_with_resource highest_contact = gajim.contacts.get_contact_with_highest_priority( account, jid) # Look for a chat control that has the given resource, or default to one # without resource - ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account) - if ctrl: - chat_control = ctrl - elif not pm and (not highest_contact or not highest_contact.resource): + chat_control = session.get_control() +# ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account, session.thread_id) +# if ctrl: +# chat_control = ctrl +# elif not pm and (not highest_contact or not highest_contact.resource): # unknow contact or offline message - jid_of_control = jid - chat_control = self.msg_win_mgr.get_control(jid, account) - elif highest_contact and resource != highest_contact.resource and \ - highest_contact.show != 'offline': - jid_of_control = full_jid_with_resource - chat_control = None - elif not pm: - jid_of_control = jid - chat_control = self.msg_win_mgr.get_control(jid, account) +# jid_of_control = jid +# chat_control = self.msg_win_mgr.get_control(jid, account, session.thread_id) +# elif highest_contact and resource != highest_contact.resource and \ +# highest_contact.show != 'offline': +# jid_of_control = full_jid_with_resource +# chat_control = None +# elif not pm: +# jid_of_control = jid +# chat_control = self.msg_win_mgr.get_control(jid, account, session.thread_id) - # Handle chat states + # Handle chat states contact = gajim.contacts.get_contact(account, jid, resource) if contact and isinstance(contact, list): contact = contact[0] @@ -739,7 +740,7 @@ class Interface: # got no valid jep85 answer, peer does not support it contact.chatstate = False elif chatstate == 'active': - # Brand new message, incoming. + # Brand new message, incoming. contact.our_chatstate = chatstate contact.chatstate = chatstate if msg_id: # Do not overwrite an existing msg_id with None @@ -753,10 +754,12 @@ class Interface: if gajim.config.get('ignore_unknown_contacts') and \ not gajim.contacts.get_contact(account, jid) and not pm: return + if not contact: # contact is not in the roster, create a fake one to display # notification - contact = common.contacts.Contact(jid = jid, resource = resource) + contact = common.contacts.Contact(jid = jid, resource = resource) + advanced_notif_num = notify.get_advanced_notification('message_received', account, contact) @@ -765,8 +768,8 @@ class Interface: if msg_type == 'normal': if not gajim.events.get_events(account, jid, ['normal']): first = True - elif not chat_control and not gajim.events.get_events(account, - jid_of_control, [msg_type]): # msg_type can be chat or pm + elif not chat_control and not gajim.events.get_events(account, + full_jid_with_resource, [msg_type]): # msg_type can be chat or pm first = True if pm: @@ -778,18 +781,19 @@ class Interface: if encrypted: self.roster.on_message(jid, message, array[2], account, array[3], msg_type, subject, resource, msg_id, array[9], - advanced_notif_num, thread = thread) + advanced_notif_num, session = session) else: # xhtml in last element self.roster.on_message(jid, message, array[2], account, array[3], msg_type, subject, resource, msg_id, array[9], - advanced_notif_num, xhtml = xhtml, thread = thread) + advanced_notif_num, xhtml = xhtml, session = session) nickname = gajim.get_name_from_jid(account, jid) - # Check and do wanted notifications + + # Check and do wanted notifications msg = message if subject: msg = _('Subject: %s') % subject + '\n' + msg - notify.notify('new_message', jid_of_control, account, [msg_type, + notify.notify('new_message', full_jid_with_resource, account, [msg_type, first, nickname, msg], advanced_notif_num) if self.remote_ctrl: @@ -986,7 +990,7 @@ class Interface: resource = '' if vcard.has_key('resource'): resource = vcard['resource'] - + # vcard window win = None if self.instances[account]['infos'].has_key(jid): @@ -1008,11 +1012,14 @@ class Interface: elif self.msg_win_mgr.has_window(jid, account): win = self.msg_win_mgr.get_window(jid, account) ctrl = win.get_control(jid, account) - if win and ctrl.type_id != message_control.TYPE_GC: - ctrl.show_avatar() + + for ctrl in self.msg_win_mgr.get_controls(jid=jid, acct=account): + if ctrl.type_id != message_control.TYPE_GC: + ctrl.show_avatar() # Show avatar in roster or gc_roster gc_ctrl = self.msg_win_mgr.get_control(jid, account) + # XXX get_gc_control? if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC: gc_ctrl.draw_avatar(resource) else: @@ -1656,25 +1663,24 @@ class Interface: AtomWindow.newAtomEntry(atom_entry) def handle_session_negotiation(self, account, data): - jid, thread_id, form = data + jid, session, form = data # XXX check negotiation state, etc. # XXX check if we can autoaccept if form.getType() == 'form': - ctrl = gajim.interface.msg_win_mgr.get_control(str(jid), account) - if not ctrl: - resource = jid.getResource() - contact = gajim.contacts.get_contact(account, str(jid), resource) - if not contact: - connection = gajim.connections[account] - contact = gajim.contacts.create_contact(jid = jid.getStripped(), resource = resource, show = connection.get_status()) - self.roster.new_chat(contact, account, resource = resource) + ctrl = session.get_control() +# ctrl = gajim.interface.msg_win_mgr.get_control(str(jid), account) +# if not ctrl: +# resource = jid.getResource() +# contact = gajim.contacts.get_contact(account, str(jid), resource) +# if not contact: +# connection = gajim.connections[account] +# contact = gajim.contacts.create_contact(jid = jid.getStripped(), resource = resource, show = connection.get_status()) +# self.roster.new_chat(contact, account, resource = resource) - ctrl = gajim.interface.msg_win_mgr.get_control(str(jid), account) +# ctrl = gajim.interface.msg_win_mgr.get_control(str(jid), account) - ctrl.set_thread_id(thread_id) - - negotiation.FeatureNegotiationWindow(account, jid, thread_id, form) + negotiation.FeatureNegotiationWindow(account, jid, session, form) def handle_event_privacy_lists_received(self, account, data): # ('PRIVACY_LISTS_RECEIVED', account, list) diff --git a/src/message_control.py b/src/message_control.py index 11b3b6996..c0b409b11 100644 --- a/src/message_control.py +++ b/src/message_control.py @@ -37,8 +37,6 @@ class MessageControl: self.hide_chat_buttons_current = False self.resource = resource - self.thread_id = self.contact.get_session() - gajim.last_message_time[self.account][self.get_full_jid()] = 0 self.xml = gtkgui_helpers.get_glade('message_window.glade', widget_name) @@ -112,14 +110,6 @@ class MessageControl: def get_specific_unread(self): return len(gajim.events.get_events(self.account, self.contact.jid)) - def set_thread_id(self, thread_id): - if thread_id == self.thread_id: - return - if self.thread_id: - print "starting a new session, forgetting about the old one!" - self.thread_id = thread_id - self.contact.sessions[self.contact.get_full_jid()] = thread_id - def send_message(self, message, keyID = '', type = 'chat', chatstate = None, msg_id = None, composing_jep = None, resource = None, user_nick = None): @@ -127,11 +117,8 @@ class MessageControl: ''' jid = self.contact.jid - if not self.thread_id: - self.thread_id = self.contact.new_session() - # Send and update history return gajim.connections[self.account].send_message(jid, message, keyID, type = type, chatstate = chatstate, msg_id = msg_id, composing_jep = composing_jep, resource = self.resource, - user_nick = user_nick, thread = self.thread_id) + user_nick = user_nick, session = self.session) diff --git a/src/message_window.py b/src/message_window.py index 6445495be..87e9a204f 100644 --- a/src/message_window.py +++ b/src/message_window.py @@ -123,8 +123,9 @@ class MessageWindow: def get_num_controls(self): n = 0 - for dict in self._controls.values(): - n += len(dict) + for sess_dict in self._controls.values(): + for dict in sess_dict.values(): + n += len(dict) return n def _on_window_focus(self, widget, event): @@ -165,7 +166,9 @@ class MessageWindow: if not self._controls.has_key(control.account): self._controls[control.account] = {} fjid = control.get_full_jid() - self._controls[control.account][fjid] = control + if not self._controls.has_key(fjid): + self._controls[control.account][fjid] = {} + self._controls[control.account][fjid][control.session.thread_id] = control if self.get_num_controls() == 2: # is first conversation_textview scrolled down ? @@ -292,11 +295,11 @@ class MessageWindow: else: gtkgui_helpers.set_unset_urgency_hint(self.window, False) - def set_active_tab(self, jid, acct): - ctrl = self._controls[acct][jid] + def set_active_tab(self, jid, acct, thread_id): + ctrl = self._controls[acct][jid][thread_id] ctrl_page = self.notebook.page_num(ctrl.widget) self.notebook.set_current_page(ctrl_page) - + def remove_tab(self, ctrl, method, reason = None, force = False): '''reason is only for gc (offline status message) if force is True, do not ask any confirmation''' @@ -317,7 +320,9 @@ class MessageWindow: self.notebook.remove_page(self.notebook.page_num(ctrl.widget)) fjid = ctrl.get_full_jid() - del self._controls[ctrl.account][fjid] + del self._controls[ctrl.account][fjid][ctrl.session.thread_id] + if len(self._controls[ctrl.account][fjid]) == 0: + del self._controls[ctrl.account][fjid] if len(self._controls[ctrl.account]) == 0: del self._controls[ctrl.account] @@ -415,7 +420,19 @@ class MessageWindow: for ctrl in self.controls(): ctrl.update_tags() - def get_control(self, key, acct): + def has_control(self, jid, acct, thread_id = None): + try: + if thread_id: + return (thread_id in self._controls[acct][jid]) + else: + return (jid in self._controls[acct]) + except KeyError: + return False + + def get_controls(self, jid, acct): + return self._controls[acct][jid].values() + + def get_control(self, key, acct, thread_id): '''Return the MessageControl for jid or n, where n is a notebook page index. When key is an int index acct may be None''' if isinstance(key, str): @@ -424,7 +441,7 @@ class MessageWindow: if isinstance(key, unicode): jid = key try: - return self._controls[acct][jid] + return self._controls[acct][jid][thread_id] except: return None else: @@ -436,9 +453,10 @@ class MessageWindow: return self._widget_to_control(nth_child) def controls(self): - for ctrl_dict in self._controls.values(): - for ctrl in ctrl_dict.values(): - yield ctrl + for jid_dict in self._controls.values(): + for sess_dict in jid_dict.values(): + for ctrl in sess_dict.values(): + yield ctrl def move_to_next_unread_tab(self, forward): ind = self.notebook.get_current_page() @@ -495,7 +513,7 @@ class MessageWindow: if old_no >= 0: old_ctrl = self._widget_to_control(notebook.get_nth_page(old_no)) old_ctrl.set_control_active(False) - + new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num)) new_ctrl.set_control_active(True) self.show_title(control = new_ctrl) @@ -569,11 +587,11 @@ class MessageWindow: source_child = self.notebook.get_nth_page(source_page_num) if dest_page_num != source_page_num: self.notebook.reorder_child(source_child, dest_page_num) - + def get_tab_at_xy(self, x, y): '''Thanks to Gaim Return the tab under xy and - if its nearer from left or right side of the tab + if its nearer from left or right side of the tab ''' page_num = -1 to_right = False @@ -594,7 +612,7 @@ class MessageWindow: if (y >= tab_alloc.y) and \ (y <= (tab_alloc.y + tab_alloc.height)): page_num = i - + if y > tab_alloc.y + (tab_alloc.height / 2.0): to_right = True break @@ -659,14 +677,22 @@ class MessageWindowMgr: return w return None - def get_window(self, jid, acct): + def get_window(self, jid, acct, thread_id): for win in self.windows(): - if win.get_control(jid, acct): + if win.has_control(jid, acct, thread_id): return win return None - def has_window(self, jid, acct): - return self.get_window(jid, acct) != None + def get_windows(self, jid, acct): + for win in self.windows(): + if win.has_control(jid, acct): + yield win + + def has_window(self, jid, acct, thread_id = None): + for win in self.windows(): + if win.has_control(jid, acct, thread_id): + return True + return False def one_window_opened(self, contact, acct, type): try: @@ -678,7 +704,7 @@ class MessageWindowMgr: '''Resizes window according to config settings''' if not gajim.config.get('saveposition'): return - + if self.mode == self.ONE_MSG_WINDOW_ALWAYS: size = (gajim.config.get('msgwin-width'), gajim.config.get('msgwin-height')) @@ -695,7 +721,7 @@ class MessageWindowMgr: return gtkgui_helpers.resize_window(win.window, size[0], size[1]) - + def _position_window(self, win, acct, type): '''Moves window according to config settings''' if not gajim.config.get('saveposition') or\ @@ -773,18 +799,20 @@ class MessageWindowMgr: del self._windows[k] return - def get_control(self, jid, acct): + def get_control(self, jid, acct, thread_id): '''Amongst all windows, return the MessageControl for jid''' - win = self.get_window(jid, acct) + win = self.get_window(jid, acct, thread_id) if win: - return win.get_control(jid, acct) + return win.get_control(jid, acct, thread_id) return None - def get_controls(self, type = None, acct = None): + def get_controls(self, type = None, acct = None, jid = None): ctrls = [] for c in self.controls(): if acct and c.account != acct: continue + if jid and c.get_full_jid() != jid: + continue if not type or c.type_id == type: ctrls.append(c) return ctrls @@ -808,7 +836,7 @@ class MessageWindowMgr: def save_state(self, msg_win): if not gajim.config.get('saveposition'): return - + # Save window size and position pos_x_key = 'msgwin-x-position' pos_y_key = 'msgwin-y-position' @@ -843,11 +871,11 @@ class MessageWindowMgr: if self.mode != self.ONE_MSG_WINDOW_NEVER: gajim.config.set_per('accounts', acct, pos_x_key, x) gajim.config.set_per('accounts', acct, pos_y_key, y) - + else: gajim.config.set(size_width_key, width) gajim.config.set(size_height_key, height) - + if self.mode != self.ONE_MSG_WINDOW_NEVER: gajim.config.set(pos_x_key, x) gajim.config.set(pos_y_key, y) diff --git a/src/negotiation.py b/src/negotiation.py index 32fbf0a3a..095fd5178 100644 --- a/src/negotiation.py +++ b/src/negotiation.py @@ -7,11 +7,11 @@ from common import xmpp class FeatureNegotiationWindow: '''FeatureNegotiotionWindow class''' - def __init__(self, account, jid, thread_id, form): + def __init__(self, account, jid, session, form): self.account = account self.jid = jid self.form = form - self.thread_id = thread_id + self.session = session self.xml = gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_window') self.window = self.xml.get_widget('data_form_window') @@ -27,7 +27,7 @@ class FeatureNegotiationWindow: def on_ok_button_clicked(self, widget): acceptance = xmpp.Message(self.jid) - acceptance.setThread(self.thread_id) + acceptance.setThread(self.session.thread_id) feature = acceptance.NT.feature feature.setNamespace(xmpp.NS_FEATURE) @@ -44,7 +44,7 @@ class FeatureNegotiationWindow: # XXX determine whether to reveal presence rejection = xmpp.Message(self.jid) - rejection.setThread(self.thread_id) + rejection.setThread(self.session.thread_id) feature = rejection.NT.feature feature.setNamespace(xmpp.NS_FEATURE) diff --git a/src/notify.py b/src/notify.py index 66b130791..8dda70f69 100644 --- a/src/notify.py +++ b/src/notify.py @@ -40,7 +40,7 @@ try: except ImportError: USER_HAS_PYNOTIFY = False -def get_show_in_roster(event, account, contact): +def get_show_in_roster(event, account, contact, session): '''Return True if this event must be shown in roster, else False''' if event == 'gc_message_received': return True @@ -51,7 +51,7 @@ def get_show_in_roster(event, account, contact): if gajim.config.get_per('notifications', str(num), 'roster') == 'no': return False if event == 'message_received': - chat_control = helpers.get_chat_control(account, contact) + chat_control = helpers.get_chat_control(account, contact, session) if chat_control: return False return True diff --git a/src/roster_window.py b/src/roster_window.py index 4f0019aa5..139facee8 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -1190,10 +1190,14 @@ class RosterWindow: '''reads from db the unread messages, and fire them up''' for jid in gajim.contacts.get_jid_list(account): results = gajim.logger.get_unread_msgs_for_jid(jid) + + # XXX results should contain sessions anyways + session = gajim.connections[account].make_new_session(jid) + for result in results: tim = time.localtime(float(result[2])) self.on_message(jid, result[1], tim, account, msg_type = 'chat', - msg_id = result[0]) + msg_id = result[0], session = session) def fill_contacts_and_groups_dicts(self, array, account): '''fill gajim.contacts and gajim.groups''' @@ -1255,8 +1259,8 @@ class RosterWindow: gajim.transport_avatar[account][host].append(contact1.jid) # If we already have a chat window opened, update it with new contact # instance - chat_control = gajim.interface.msg_win_mgr.get_control(ji, account) - if chat_control: + chat_controls = gajim.interface.msg_win_mgr.get_controls(jid=ji, acct=account) + for chat_control in chat_controls: chat_control.contact = contact1 def chg_contact_status(self, contact, show, status, account): @@ -1283,14 +1287,14 @@ class RosterWindow: jid_list = [contact.jid] for jid in jid_list: if gajim.interface.msg_win_mgr.has_window(jid, account): - win = gajim.interface.msg_win_mgr.get_window(jid, account) - ctrl = win.get_control(jid, account) - ctrl.contact = gajim.contacts.get_contact_with_highest_priority( - account, contact.jid) - ctrl.update_ui() - win.redraw_tab(ctrl) + for win in gajim.interface.msg_win_mgr.get_windows(jid, account): + for ctrl in win.get_controls(jid=jid, acct=account): + ctrl.contact = gajim.contacts.get_contact_with_highest_priority( + account, contact.jid) + ctrl.update_ui() + win.redraw_tab(ctrl) - name = contact.get_shown_name() + name = contact.get_shown_name() # if multiple resources (or second one disconnecting) if (len(contact_instances) > 1 or (len(contact_instances) == 1 and \ @@ -3477,18 +3481,22 @@ class RosterWindow: # We call this here to avoid race conditions with widget validation chat_control.read_queue() - def new_chat(self, contact, account, resource = None): + def new_chat(self, contact, account, resource = None, session = None): # Get target window, create a control, and associate it with the window type_ = message_control.TYPE_CHAT fjid = contact.jid if resource: fjid += '/' + resource - mw = gajim.interface.msg_win_mgr.get_window(fjid, account) + + if not session: + session = gajim.connections[account].make_new_session(fjid) + + mw = gajim.interface.msg_win_mgr.get_window(fjid, account, session.thread_id) if not mw: mw = gajim.interface.msg_win_mgr.create_window(contact, account, type_) - chat_control = ChatControl(mw, contact, account, resource) + chat_control = ChatControl(mw, contact, account, session, resource) mw.new_tab(chat_control) @@ -3496,6 +3504,8 @@ class RosterWindow: # We call this here to avoid race conditions with widget validation chat_control.read_queue() + return session + def new_chat_from_jid(self, account, fjid): jid, resource = gajim.get_room_and_nick_from_fjid(fjid) if resource: @@ -3531,7 +3541,7 @@ class RosterWindow: def on_message(self, jid, msg, tim, account, encrypted = False, msg_type = '', subject = None, resource = '', msg_id = None, - user_nick = '', advanced_notif_num = None, xhtml = None, thread = None): + user_nick = '', advanced_notif_num = None, xhtml = None, session = None): '''when we receive a message''' contact = None # if chat window will be for specific resource @@ -3569,16 +3579,18 @@ class RosterWindow: path = self.get_path(jid, account) # Try to get line of contact in roster + ctrl = session.get_control(advanced_notif_num) + # Look for a chat control that has the given resource - ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) - if not ctrl: +# ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) +# if not ctrl: # if not, if message comes from highest prio, get control or open one # without resource - if highest_contact and contact.resource == highest_contact.resource \ - and not jid == gajim.get_jid_from_account(account): - ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) - fjid = jid - resource_for_chat = None +# if highest_contact and contact.resource == highest_contact.resource \ +# and not jid == gajim.get_jid_from_account(account): +# ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) +# fjid = jid +# resource_for_chat = None # Do we have a queue? no_queue = len(gajim.events.get_events(account, fjid)) == 0 @@ -3588,7 +3600,7 @@ class RosterWindow: if msg_type == 'normal' and popup: # it's single message to be autopopuped dialogs.SingleMessageWindow(account, contact.jid, action = 'receive', from_whom = jid, subject = subject, - message = msg, resource = resource, thread = thread) + message = msg, resource = resource, session = session) return # We print if window is opened and it's not a single message @@ -3596,8 +3608,6 @@ class RosterWindow: typ = '' if msg_type == 'error': typ = 'status' - if thread: - ctrl.set_thread_id(thread) ctrl.print_conversation(msg, typ, tim = tim, encrypted = encrypted, subject = subject, xhtml = xhtml) if msg_id: @@ -3610,26 +3620,26 @@ class RosterWindow: if msg_type == 'normal': type_ = 'normal' event_type = 'single_message_received' - show_in_roster = notify.get_show_in_roster(event_type, account, contact) + show_in_roster = notify.get_show_in_roster(event_type, account, contact, session) show_in_systray = notify.get_show_in_systray(event_type, account, contact) event = gajim.events.create_event(type_, (msg, subject, msg_type, tim, - encrypted, resource, msg_id, xhtml, thread), show_in_roster = show_in_roster, + encrypted, resource, msg_id, xhtml, session), show_in_roster = show_in_roster, show_in_systray = show_in_systray) gajim.events.add_event(account, fjid, event) - if popup: - if not ctrl: - self.new_chat(contact, account, resource = resource_for_chat) - if path and not self.dragging and gajim.config.get( - 'scroll_roster_to_last_message'): - # we curently see contact in our roster OR he - # is not in the roster at all. +# if popup: +# if not ctrl: +# self.new_chat(contact, account, resource = resource_for_chat) +# if path and not self.dragging and gajim.config.get( +# 'scroll_roster_to_last_message'): +# # we curently see contact in our roster OR he +# # is not in the roster at all. # show and select his line in roster # do not change selection while DND'ing - self.tree.expand_row(path[0:1], False) - self.tree.expand_row(path[0:2], False) - self.tree.scroll_to_cell(path) - self.tree.set_cursor(path) - else: +# self.tree.expand_row(path[0:1], False) +# self.tree.expand_row(path[0:2], False) +# self.tree.scroll_to_cell(path) +# self.tree.set_cursor(path) + if not popup: if no_queue: # We didn't have a queue: we change icons self.draw_contact(jid, account) self.show_title() # we show the * or [n] @@ -3875,7 +3885,7 @@ class RosterWindow: if event.type_ == 'normal': dialogs.SingleMessageWindow(account, jid, action = 'receive', from_whom = jid, subject = data[1], - message = data[0], resource = data[5], thread = data[8]) + message = data[0], resource = data[5], session = data[8]) gajim.interface.remove_first_event(account, jid, event.type_) return True elif event.type_ == 'file-request': @@ -3912,22 +3922,22 @@ class RosterWindow: jid = jid + u'/' + resource adhoc_commands.CommandWindow(account, jid) - def on_open_chat_window(self, widget, contact, account, resource = None): + def on_open_chat_window(self, widget, contact, account, resource = None, session = None): # Get the window containing the chat fjid = contact.jid if resource: fjid += '/' + resource - win = gajim.interface.msg_win_mgr.get_window(fjid, account) - if not win: - self.new_chat(contact, account, resource = resource) - win = gajim.interface.msg_win_mgr.get_window(fjid, account) - ctrl = win.get_control(fjid, account) - # last message is long time ago - gajim.last_message_time[account][ctrl.get_full_jid()] = 0 - win.set_active_tab(fjid, account) + + session = self.new_chat(contact, account, resource=resource, session=session) + win = gajim.interface.msg_win_mgr.get_window(fjid, account, session.thread_id) + ctrl = win.get_control(fjid, account, session.thread_id) + # last message is long time ago + gajim.last_message_time[account][ctrl.get_full_jid()] = 0 + + win.set_active_tab(fjid, account, session.thread_id) if gajim.connections[account].is_zeroconf and \ gajim.connections[account].status in ('offline', 'invisible'): - win.get_control(fjid, account).got_disconnected() + win.get_control(fjid, account, session.thread_id).got_disconnected() win.window.present() @@ -3967,7 +3977,10 @@ class RosterWindow: jid = child_jid else: child_iter = model.iter_next(child_iter) + + session = None if first_ev: + session = first_ev.parameters[8] fjid = jid if resource: fjid += '/' + resource @@ -3978,7 +3991,7 @@ class RosterWindow: c = gajim.contacts.get_contact_with_highest_priority(account, jid) if jid == gajim.get_jid_from_account(account): resource = c.resource - self.on_open_chat_window(widget, c, account, resource = resource) + self.on_open_chat_window(widget, c, account, resource = resource, session=session) def on_roster_treeview_row_activated(self, widget, path, col = 0): '''When an iter is double clicked: open the first event window'''