Author: bct Date: 2008-05-24 01:27:08 +0200 (Sat, 24 May 2008) New Revision: 9695
Modified: trunk/src/chat_control.py trunk/src/common/connection.py trunk/src/common/connection_handlers.py trunk/src/common/stanza_session.py trunk/src/gajim.py trunk/src/message_control.py trunk/src/message_window.py trunk/src/roster_window.py trunk/src/session.py Log: reuse chat controls after session termination fixes #3950
Modified: trunk/src/chat_control.py =================================================================== --- trunk/src/chat_control.py 2008-05-23 23:26:53 UTC (rev 9694) +++ trunk/src/chat_control.py 2008-05-23 23:27:08 UTC (rev 9695) @@ -1206,6 +1206,9 @@ ChatControlBase.update_ui(self)
def get_otr_status(self): + if not self.session: + return 0 + ctx = gajim.otr_module.otrl_context_find( self.session.conn.otr_userstates, self.contact.get_full_jid().encode(), @@ -1613,7 +1616,7 @@
def print_esession_details(self): '''print esession settings to textview''' - e2e_is_active = self.session and self.session.enable_encryption + e2e_is_active = bool(self.session) and self.session.enable_encryption if e2e_is_active: msg = _('E2E encryption enabled') ChatControlBase.print_conversation_line(self, msg, 'status', '', None) @@ -2022,7 +2025,8 @@ self.contact.our_chatstate = None
# disconnect self from session - self.session.control = None + if self.session: + self.session.control = None
# Disconnect timer callbacks gobject.source_remove(self.possible_paused_timeout_id)
Modified: trunk/src/common/connection.py =================================================================== --- trunk/src/common/connection.py 2008-05-23 23:26:53 UTC (rev 9694) +++ trunk/src/common/connection.py 2008-05-23 23:27:08 UTC (rev 9695) @@ -980,6 +980,8 @@ elif show == 'offline': self.connected = 0 if self.connection: + self.terminate_sessions() + self.on_purpose = True p = common.xmpp.Presence(typ = 'unavailable') p = self.add_sha(p, False)
Modified: trunk/src/common/connection_handlers.py =================================================================== --- trunk/src/common/connection_handlers.py 2008-05-23 23:26:53 UTC (rev 9694) +++ trunk/src/common/connection_handlers.py 2008-05-23 23:27:08 UTC (rev 9695) @@ -1282,14 +1282,14 @@ self.sessions = {}
def delete_session(self, jid, thread_id): - try: - del self.sessions[jid][thread_id] + if not jid in self.sessions: + jid = gajim.get_jid_without_resource(jid)
- if not self.sessions[jid]: - del self.sessions[jid] - except KeyError: - pass + del self.sessions[jid][thread_id]
+ if not self.sessions[jid]: + del self.sessions[jid] + def find_null_session(self, jid): '''finds all of the sessions between us and a remote jid in which we haven't received a thread_id yet and returns the session that we last @@ -1644,17 +1644,17 @@ mtype = 'normal'
msgtxt = msg.getBody() - subject = msg.getSubject() # if not there, it's None
jid = helpers.get_jid_from_iq(msg)
encrypted = False + xep_200_encrypted = msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO)
# I don't trust libotr, that's why I only pass the # message to it if it either contains the magic # ?OTR string or a plaintext tagged message. - if gajim.otr_module and \ - isinstance(msgtxt, unicode) and \ + if gajim.otr_module and not xep_200_encrypted \ + and isinstance(msgtxt, unicode) and \ ('\x20\x09\x20\x20\x09\x09\x09\x09\x20\x09\x20\x09\x20\x09\x20\x20' \ in msgtxt or '?OTR' in msgtxt): # If it doesn't include ?OTR, it wasn't an @@ -1764,11 +1764,12 @@ tim = helpers.datetime_tuple(tim) tim = localtime(timegm(tim))
- if msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO): + if xep_200_encrypted: encrypted = True
try: msg = session.decrypt_stanza(msg) + msgtxt = msg.getBody() except: self.dispatch('FAILED_DECRYPT', (frm, tim, session))
@@ -1791,26 +1792,28 @@ msgtxt = decmsg.replace('\x00', '') encrypted = True if mtype == 'error': - self.dispatch_error_message(msg, msgtxt, session, frm, tim, subject) + self.dispatch_error_message(msg, msgtxt, session, frm, tim) elif mtype == 'groupchat': - self.dispatch_gc_message(msg, subject, frm, msgtxt, jid, tim) + self.dispatch_gc_message(msg, frm, msgtxt, jid, tim) elif invite is not None: self.dispatch_invite_message(invite, frm) else: if isinstance(session, ChatControlSession): - session.received(frm, msgtxt, tim, encrypted, subject, msg) + session.received(frm, msgtxt, tim, encrypted, msg) else: session.received(msg) # END messageCB
# process and dispatch an error message - def dispatch_error_message(self, msg, msgtxt, session, frm, tim, subject): + def dispatch_error_message(self, msg, msgtxt, session, frm, tim): error_msg = msg.getErrorMsg()
if not error_msg: error_msg = msgtxt msgtxt = None
+ subject = msg.getSubject() + if session.is_loggable(): try: gajim.logger.write('error', frm, error_msg, tim=tim, @@ -1821,9 +1824,11 @@ tim, session))
# process and dispatch a groupchat message - def dispatch_gc_message(self, msg, subject, frm, msgtxt, jid, tim): + def dispatch_gc_message(self, msg, frm, msgtxt, jid, tim): has_timestamp = bool(msg.timestamp)
+ subject = msg.getSubject() + if subject is not None: self.dispatch('GC_SUBJECT', (frm, subject, msgtxt, has_timestamp)) return
Modified: trunk/src/common/stanza_session.py =================================================================== --- trunk/src/common/stanza_session.py 2008-05-23 23:26:53 UTC (rev 9694) +++ trunk/src/common/stanza_session.py 2008-05-23 23:27:08 UTC (rev 9695) @@ -243,6 +243,9 @@
return stanza
+ def is_xep_200_encrypted(self, msg): + msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO) + def hmac(self, key, content): return HMAC.new(key, content, self.hash_alg).digest()
Modified: trunk/src/gajim.py =================================================================== --- trunk/src/gajim.py 2008-05-23 23:26:53 UTC (rev 9694) +++ trunk/src/gajim.py 2008-05-23 23:27:08 UTC (rev 9695) @@ -2038,31 +2038,13 @@
if form.getField('terminate') and\ form.getField('terminate').getValue() in ('1', 'true'): - was_encrypted = session.enable_encryption - ctrl = session.control jid = str(jid)
session.acknowledge_termination() - gajim.connections[account].delete_session(jid, session.thread_id)
- if ctrl: - # replace the old session in this control with a new one - new_sess = gajim.connections[account].make_new_session(jid) - win = ctrl.parent_win + conn = gajim.connections[account] + conn.delete_session(jid, session.thread_id)
- ctrl.set_session(new_sess) - gajim.connections[account].delete_session(jid, - session.thread_id) - - if not jid in win._controls[account]: - jid = gajim.get_jid_without_resource(jid) - - del win._controls[account][jid][session.thread_id] - win._controls[account][jid][new_sess.thread_id] = ctrl - - if was_encrypted: - ctrl.print_esession_details() - return
# non-esession negotiation. this isn't very useful, but i'm keeping it around
Modified: trunk/src/message_control.py =================================================================== --- trunk/src/message_control.py 2008-05-23 23:26:53 UTC (rev 9694) +++ trunk/src/message_control.py 2008-05-23 23:27:08 UTC (rev 9695) @@ -128,16 +128,18 @@
self.session = session
+ new_key = None if session: session.control = self + new_key = session.thread_id
- if oldsession: - self.parent_win.change_thread_key( - self.contact.jid, self.account, - oldsession.thread_id, session.thread_id) + if oldsession: + self.parent_win.change_thread_key( + self.contact.jid, self.account, + oldsession.thread_id, new_key)
- if oldsession.enable_encryption: - self.print_esession_details() + if oldsession.enable_encryption: + self.print_esession_details()
def send_message(self, message, keyID = '', type = 'chat', chatstate = None, msg_id = None, composing_xep = None, resource = None, @@ -147,7 +149,14 @@ jid = self.contact.jid original_message = message
- if gajim.otr_module and jid not in gajim.otr_dont_append_tag: + if not self.session: + sess = gajim.connections[self.account].make_new_session(jid) + self.set_session(sess) + self.parent_win.move_from_sessionless(self) + + xep_200 = bool(self.session) and self.session.enable_encryption + + if gajim.otr_module and not xep_200 and (jid not in gajim.otr_dont_append_tag): if type == 'chat' and isinstance(message, unicode): d = {'kwargs': {'keyID': keyID, 'type': type, 'chatstate': chatstate,
Modified: trunk/src/message_window.py =================================================================== --- trunk/src/message_window.py 2008-05-23 23:26:53 UTC (rev 9694) +++ trunk/src/message_window.py 2008-05-23 23:27:08 UTC (rev 9695) @@ -54,8 +54,15 @@ ) = range(5)
def __init__(self, acct, type, parent_window=None, parent_paned=None): - # A dictionary of dictionaries where _contacts[account][jid] == A MessageControl + # A dictionary of dictionaries of dictionaries + # where _contacts[account][jid][thread_id] == A MessageControl self._controls = {} + + # a dictionary of dictionaries where + # sessionless_ctrls[account][jid] = a list of MessageControls that don't have + # sessions attached + self.sessionless_ctrls = {} + # If None, the window is not tied to any specific account self.account = acct # If None, the window is not tied to any specific type @@ -146,6 +153,11 @@ if self._controls.has_key(old_name): self._controls[new_name] = self._controls[old_name] del self._controls[old_name] + + if self.sessionless_ctrls.has_key(old_name): + self.sessionless_ctrls[new_name] = self.sessionless_ctrls[old_name] + del self.sessionless_ctrls[old_name] + for ctrl in self.controls(): if ctrl.account == old_name: ctrl.account = new_name @@ -157,6 +169,11 @@ for jid_dict in self._controls.values(): for dict in jid_dict.values(): n += len(dict) + + for jid_dict in self.sessionless_ctrls.values(): + for ctrls in jid_dict.values(): + n += len(ctrls) + return n
def resize(self, width, height): @@ -197,6 +214,7 @@ for ctrl in self.controls(): ctrl.shutdown() self._controls.clear() + self.sessionless_ctrls.clear() # Clean up handlers connected to the parent window, this is important since # self.window may be the RosterWindow for i in self.handlers.keys(): @@ -206,15 +224,25 @@ del self.handlers
def new_tab(self, control): - if not self._controls.has_key(control.account): - self._controls[control.account] = {} fjid = control.get_full_jid()
- if not self._controls[control.account].has_key(fjid): - self._controls[control.account][fjid] = {} + if control.session: + if not self._controls.has_key(control.account): + self._controls[control.account] = {}
- self._controls[control.account][fjid][control.session.thread_id] = control + if not self._controls[control.account].has_key(fjid): + self._controls[control.account][fjid] = {}
+ self._controls[control.account][fjid][control.session.thread_id] = control + else: + if not self.sessionless_ctrls.has_key(control.account): + self.sessionless_ctrls[control.account] = {} + + if not self.sessionless_ctrls[control.account].has_key(fjid): + self.sessionless_ctrls[control.account][fjid] = [] + + self.sessionless_ctrls[control.account][fjid].append(control) + if self.get_num_controls() == 2: # is first conversation_textview scrolled down ? scrolled = False @@ -443,7 +471,6 @@
fjid = ctrl.get_full_jid() jid = gajim.get_jid_without_resource(fjid) - thread_id = ctrl.session.thread_id
fctrls = self.get_controls(fjid, ctrl.account) bctrls = self.get_controls(jid, ctrl.account) @@ -458,14 +485,21 @@
self.notebook.remove_page(self.notebook.page_num(ctrl.widget))
- del self._controls[ctrl.account][fjid][thread_id] + if ctrl.session: + dict = self._controls + idx = ctrl.session.thread_id + else: + dict = self.sessionless_ctrls + idx = dict[ctrl.account][fjid].index(ctrl)
- if len(self._controls[ctrl.account][fjid]) == 0: - del self._controls[ctrl.account][fjid] + del dict[ctrl.account][fjid][idx]
- if len(self._controls[ctrl.account]) == 0: - del self._controls[ctrl.account] + if len(dict[ctrl.account][fjid]) == 0: + del dict[ctrl.account][fjid]
+ if len(dict[ctrl.account]) == 0: + del dict[ctrl.account] + self.check_tabs() self.show_title()
@@ -591,8 +625,17 @@
def get_controls(self, jid, acct): try: - return self._controls[acct][jid].values() + sessioned = self._controls[acct][jid].values() except KeyError: + sessioned = [] + + sessionless = self.sessionless_controls(acct, jid) + return sessioned + sessionless + + def sessionless_controls(self, acct, jid): + try: + return self.sessionless_ctrls[acct][jid] + except KeyError: return []
def change_key(self, old_jid, new_jid, acct): @@ -604,27 +647,70 @@ return self._controls[acct][new_jid] = ctrls del self._controls[acct][old_jid] + + try: + ctrls = self.sessionless_ctrls[acct][old_jid] + except KeyError: + return + + self.sessionless_ctrls[acct][new_jid] = ctrls + del self.sessionless_ctrls[acct][new_jid] + if old_jid in gajim.last_message_time[acct]: gajim.last_message_time[acct][new_jid] = \ gajim.last_message_time[acct][old_jid] del gajim.last_message_time[acct][old_jid]
- def change_thread_key(self, jid, acct, old_thread_id, new_thread_id): - '''Change the thread_id key of a control''' - try: - # Check if control exists - ctrl = self._controls[acct][jid][old_thread_id] - except KeyError: - return + def change_thread_key(self, jid, acct, old_thread_id, new_thread_id): + '''Change the thread_id key of a control'''
- self._controls[acct][jid][new_thread_id] = ctrl + if jid in self._controls[acct]: + ctrl = self._controls[acct][jid][old_thread_id] + else: + jid = gajim.get_jid_without_resource(jid) + ctrl = self._controls[acct][jid][old_thread_id] + del self._controls[acct][jid][old_thread_id]
+ if new_thread_id: + self._controls[acct][jid][new_thread_id] = ctrl + else: + if acct not in self.sessionless_ctrls: + self.sessionless_ctrls[acct] = {} + + if jid not in self.sessionless_ctrls[acct]: + self.sessionless_ctrls[acct][jid] = [] + + self.sessionless_ctrls[acct][jid].append(ctrl) + + def move_from_sessionless(self, ctrl): + '''a control just got a session, move it to the proper holding cell''' + acct = ctrl.account + jid = ctrl.get_full_jid() + + idx = self.sessionless_ctrls[acct][jid].index(ctrl) + + del self.sessionless_ctrls[acct][jid][idx] + + if not self._controls.has_key(acct): + self._controls[acct] = {} + + if not self.sessionless_ctrls[acct].has_key(jid): + self._controls[acct][jid] = {} + + thread_id = ctrl.session.thread_id + + self._controls[acct][jid][thread_id] = ctrl + def controls(self): for jid_dict in self._controls.values(): for ctrl_dict in jid_dict.values(): for ctrl in ctrl_dict.values(): yield ctrl + for jid_dict in self.sessionless_ctrls.values(): + for ctrl_dict in jid_dict.values(): + for ctrl in ctrl_dict: + yield ctrl
def move_to_next_unread_tab(self, forward): ind = self.notebook.get_current_page() @@ -834,11 +920,10 @@
def get_window(self, jid, acct): for win in self.windows(): - try: - if win._controls[acct][jid]: + if (acct in win._controls and jid in win._controls[acct]) or \ + (acct in win.sessionless_ctrls and jid in win.sessionless_ctrls[acct]): return win - except KeyError: - pass + return None
def get_gc_control(self, jid, acct):
Modified: trunk/src/roster_window.py =================================================================== --- trunk/src/roster_window.py 2008-05-23 23:26:53 UTC (rev 9694) +++ trunk/src/roster_window.py 2008-05-23 23:27:08 UTC (rev 9695) @@ -1830,7 +1830,7 @@ if was_invisible and status != 'offline': # We come back from invisible, join bookmarks gajim.interface.auto_join_bookmarks(account) - + def chg_contact_status(self, contact, show, status, account): '''When a contact changes his or her status''' contact_instances = gajim.contacts.get_contacts(account, contact.jid) @@ -2100,8 +2100,6 @@
self.quit_on_next_offline = 0 for acct in accounts: - gajim.connections[acct].terminate_sessions() - if gajim.connections[acct].connected: self.quit_on_next_offline += 1 self.send_status(acct, 'offline', message) @@ -3214,7 +3212,7 @@ import tictactoe
sess = gajim.connections[account].make_new_session(jid, - klass=tictactoe.TicTacToeSession) + cls=tictactoe.TicTacToeSession) sess.begin()
def on_execute_command(self, widget, contact, account, resource=None):
Modified: trunk/src/session.py =================================================================== --- trunk/src/session.py 2008-05-23 23:26:53 UTC (rev 9694) +++ trunk/src/session.py 2008-05-23 23:27:08 UTC (rev 9695) @@ -20,12 +20,20 @@ self.control = None
def acknowledge_termination(self): - # the other party terminated the session. we'll keep the control around, though. stanza_session.EncryptedStanzaSession.acknowledge_termination(self)
if self.control: - self.control.session = None + if self.enable_encryption: + self.control.print_esession_details()
+ self.control.set_session(None) + + def terminate(self): + stanza_session.EncryptedStanzaSession.terminate(self) + + if self.control: + self.control.set_session(None) + # extracts chatstate from a <message/> stanza def get_chatstate(self, msg, msgtxt): composing_xep = None @@ -53,8 +61,9 @@ return (composing_xep, chatstate)
# dispatch a received <message> stanza - def received(self, full_jid_with_resource, msgtxt, tim, encrypted, subject, msg): + def received(self, full_jid_with_resource, msgtxt, tim, encrypted, msg): msg_type = msg.getType() + subject = msg.getSubject()
if not msg_type: msg_type = 'normal' @@ -247,6 +256,19 @@ self.conn.name, jid, user_nick)
if not self.control: + # look for an existing chat control without a session + mw = gajim.interface.msg_win_mgr.get_window(jid, self.conn.name) + + if mw: + ctrls = mw.sessionless_controls(self.conn.name, jid) + + if len(ctrls): + ctrl = ctrls[0] + self.control = ctrl + ctrl.set_session(self) + ctrl.parent_win.move_from_sessionless(ctrl) + + if not self.control: # if no control exists and message comes from highest prio, the new # control shouldn't have a resource if highest_contact and contact.resource == highest_contact.resource \