reuse chat controls after session termination

fixes #3950
This commit is contained in:
Brendan Taylor 2008-05-23 23:27:08 +00:00
parent f68ffc3816
commit f7874d29c7
9 changed files with 186 additions and 76 deletions

View File

@ -1206,6 +1206,9 @@ class ChatControl(ChatControlBase):
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 @@ class ChatControl(ChatControlBase):
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 @@ class ChatControl(ChatControlBase):
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)

View File

@ -980,6 +980,8 @@ class Connection(ConnectionHandlers):
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)

View File

@ -1282,13 +1282,13 @@ class ConnectionHandlersBase:
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
@ -1644,17 +1644,17 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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

View File

@ -243,6 +243,9 @@ class EncryptedStanzaSession(StanzaSession):
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()

View File

@ -2038,30 +2038,12 @@ class Interface:
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
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()
conn = gajim.connections[account]
conn.delete_session(jid, session.thread_id)
return

View File

@ -128,16 +128,18 @@ class MessageControl:
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 @@ class MessageControl:
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,

View File

@ -54,8 +54,15 @@ class MessageWindow(object):
) = 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 @@ class MessageWindow(object):
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 @@ class MessageWindow(object):
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 @@ class MessageWindow(object):
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,14 +224,24 @@ class MessageWindow(object):
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 ?
@ -443,7 +471,6 @@ class MessageWindow(object):
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,13 +485,20 @@ class MessageWindow(object):
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,7 +625,16 @@ class MessageWindow(object):
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 []
@ -604,27 +647,70 @@ class MessageWindow(object):
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'''
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]
self._controls[acct][jid][new_thread_id] = ctrl
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 @@ class MessageWindowMgr(gobject.GObject):
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):

View File

@ -1830,7 +1830,7 @@ class RosterWindow:
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 @@ class RosterWindow:
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 @@ class RosterWindow:
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):

View File

@ -20,11 +20,19 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
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):
@ -53,8 +61,9 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
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'
@ -246,6 +255,19 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
contact = gajim.interface.roster.add_to_not_in_the_roster(
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