Author: asterix
Date: 2009-02-06 20:01:36 +0100 (Fri, 06 Feb 2009)
New Revision: 11055
Modified:
trunk/src/chat_control.py
trunk/src/common/connection.py
trunk/src/common/connection_handlers.py
trunk/src/common/gajim.py
trunk/src/common/xmpp/dispatcher_nb.py
trunk/src/common/zeroconf/client_zeroconf.py
trunk/src/common/zeroconf/connection_zeroconf.py
trunk/src/gajim.py
trunk/src/message_control.py
Log:
encrypt and decrypt GPG messages in a thread, and call a callback when it's finished (sending a message is now asyncronous). Fixes #4445
Modified: trunk/src/chat_control.py
===================================================================
--- trunk/src/chat_control.py 2009-02-03 15:10:56 UTC (rev 11054)
+++ trunk/src/chat_control.py 2009-02-06 19:01:36 UTC (rev 11055)
@@ -312,6 +312,7 @@
spell.set_language(lang)
except (gobject.GError, RuntimeError):
dialogs.AspellDictError(lang)
+
def on_msg_textview_populate_popup(self, textview, menu):
'''we override the default context menu and we prepend an option to switch languages'''
def _on_select_dictionary(widget, lang):
@@ -594,21 +595,19 @@
return True
return False
- def send_message(self, message, keyID = '', type_ = 'chat', chatstate = None,
- msg_id = None, composing_xep = None, resource = None,
- process_command = True, xhtml = None):
+ def send_message(self, message, keyID='', type_='chat', chatstate=None,
+ msg_id=None, composing_xep=None, resource=None, process_command=True,
+ xhtml=None, callback=None, callback_args=[]):
'''Send the given message to the active tab. Doesn't return None if error
'''
if not message or message == '\n':
return None
- ret = None
-
if not process_command or not self._process_command(message):
- ret = MessageControl.send_message(self, message, keyID, type_ = type_,
- chatstate = chatstate, msg_id = msg_id,
- composing_xep = composing_xep, resource = resource,
- user_nick = self.user_nick, xhtml = xhtml)
+ MessageControl.send_message(self, message, keyID, type_=type_,
+ chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
+ resource=resource, user_nick=self.user_nick, xhtml=xhtml,
+ callback=callback, callback_args=callback_args)
# Record message history
self.save_sent_message(message)
@@ -620,8 +619,6 @@
message_buffer = self.msg_textview.get_buffer()
message_buffer.set_text('') # clear message buffer (and tv of course)
- return ret
-
def save_sent_message(self, message):
# save the message, so user can scroll though the list with key up/down
size = len(self.sent_history)
@@ -1802,11 +1799,7 @@
gobject.source_remove(self.possible_inactive_timeout_id)
self._schedule_activity_timers()
- id_ = ChatControlBase.send_message(self, message, keyID,
- type_ = 'chat', chatstate = chatstate_to_send,
- composing_xep = composing_xep,
- process_command = process_command, xhtml = xhtml)
- if id_:
+ def _on_sent(id_, contact, message, encrypted, xhtml):
# XXX: Once we have fallback to disco, remove notexistant check
if gajim.capscache.is_supported(contact, NS_RECEIPTS) \
and not gajim.capscache.is_supported(contact,
@@ -1820,6 +1813,11 @@
encrypted = encrypted, xep0184_id = xep0184_id,
xhtml = xhtml)
+ ChatControlBase.send_message(self, message, keyID, type_='chat',
+ chatstate=chatstate_to_send, composing_xep=composing_xep,
+ process_command=process_command, xhtml=xhtml, callback=_on_sent,
+ callback_args=[contact, message, encrypted, xhtml])
+
def check_for_possible_paused_chatstate(self, arg):
''' did we move mouse of that window or write something in message
textview in the last 5 seconds?
Modified: trunk/src/common/connection.py
===================================================================
--- trunk/src/common/connection.py 2009-02-03 15:10:56 UTC (rev 11054)
+++ trunk/src/common/connection.py 2009-02-06 19:01:36 UTC (rev 11055)
@@ -1135,7 +1135,7 @@
def send_message(self, jid, msg, keyID, type_='chat', subject='',
chatstate=None, msg_id=None, composing_xep=None, resource=None,
user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
- original_message=None, delayed=None):
+ original_message=None, delayed=None, callback=None, callback_args=[]):
if not self.connection or self.connected < 2:
return 1
try:
@@ -1151,7 +1151,7 @@
from common.rst_xhtml_generator import create_xhtml
xhtml = create_xhtml(msg)
if not msg and chatstate is None and form_node is None:
- return 2
+ return
fjid = jid
if resource:
fjid += '/' + resource
@@ -1168,30 +1168,61 @@
elif keyID.endswith('MISMATCH'):
error = _('The contact\'s key (%s) does not match the key assigned in Gajim.' % keyID[:8])
else:
- #encrypt
- msgenc, error = self.gpg.encrypt(msg, [keyID])
- if msgenc and not error:
- msgtxt = '[This message is *encrypted* (See :XEP:`27`]'
- lang = os.getenv('LANG')
- if lang is not None and lang != 'en': # we're not english
- # one in locale and one en
- msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
- ' (' + msgtxt + ')'
- else:
- # Encryption failed, do not send message
- tim = localtime()
- self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session))
- return 3
+ def encrypt_thread(msg, keyID):
+ # encrypt message. This function returns (msgenc, error)
+ return self.gpg.encrypt(msg, [keyID])
+ gajim.thread_interface(encrypt_thread, [msg, keyID],
+ self._on_message_encrypted, [type_, msg, msgtxt,
+ original_message, fjid, resource, jid, xhtml, subject,
+ chatstate, composing_xep, forward_from, delayed, session,
+ form_node, user_nick, keyID, callback, callback_args])
+ return
+
+ self._on_message_encrypted(self, ('', error), type_, msg, msgtxt,
+ original_message, fjid, resource, jid, xhtml, subject, chatstate,
+ composing_xep, forward_from, delayed, session, form_node, user_nick,
+ keyID, callback, callback_args)
+
+ self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
+ resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
+ forward_from, delayed, session, form_node, user_nick, callback,
+ callback_args)
+
+ def _on_message_encrypted(self, output, type_, msg, msgtxt, original_message,
+ fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from,
+ delayed, session, form_node, user_nick, keyID, callback, callback_args):
+ msgenc, error = output
+
+ if msgenc and not error:
+ msgtxt = '[This message is *encrypted* (See :XEP:`27`]'
+ lang = os.getenv('LANG')
+ if lang is not None and lang != 'en': # we're not english
+ # one in locale and one en
+ msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
+ ' (' + msgtxt + ')'
+ self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
+ resource, jid, xhtml, subject, msgenc, keyID, chatstate,
+ composing_xep, forward_from, delayed, session, form_node, user_nick,
+ callback, callback_args)
+ return
+ # Encryption failed, do not send message
+ tim = localtime()
+ self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session))
+
+ def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
+ resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
+ forward_from, delayed, session, form_node, user_nick, callback,
+ callback_args):
if type_ == 'chat':
- msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type_,
- xhtml = xhtml)
+ msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_,
+ xhtml=xhtml)
else:
if subject:
- msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
- typ = 'normal', subject = subject, xhtml = xhtml)
+ msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
+ subject=subject, xhtml=xhtml)
else:
- msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
- typ = 'normal', xhtml = xhtml)
+ msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
+ xhtml=xhtml)
if msgenc:
msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
@@ -1206,11 +1237,9 @@
# TODO: We might want to write a function so we don't need to
# reproduce that ugly if somewhere else.
if resource:
- contact = gajim.contacts.get_contact(self.name, jid,
- resource)
+ contact = gajim.contacts.get_contact(self.name, jid, resource)
else:
- contact = gajim.contacts. \
- get_contact_with_highest_priority(self.name,
+ contact = gajim.contacts.get_contact_with_highest_priority(self.name,
jid)
# chatstates - if peer supports xep85 or xep22, send chatstates
@@ -1226,16 +1255,13 @@
not gajim.capscache.is_supported(contact,
'notexistant')):
# XEP-0085
- msg_iq.setTag(chatstate,
- namespace = common.xmpp.NS_CHATSTATES)
+ msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES)
if composing_xep in ('XEP-0022', 'asked_once') or \
not composing_xep:
# XEP-0022
- chatstate_node = msg_iq.setTag('x',
- namespace = common.xmpp.NS_EVENT)
+ chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT)
if chatstate is 'composing' or msgtxt:
- chatstate_node.addChild(
- name = 'composing')
+ chatstate_node.addChild(name='composing')
if forward_from:
addresses = msg_iq.addChild('addresses',
@@ -1255,8 +1281,7 @@
if msgtxt and gajim.config.get_per('accounts', self.name,
'request_receipt') and gajim.capscache.is_supported(contact,
common.xmpp.NS_RECEIPTS):
- msg_iq.setTag('request',
- namespace=common.xmpp.NS_RECEIPTS)
+ msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS)
if session:
# XEP-0201
@@ -1294,7 +1319,8 @@
common.logger.LOG_DB_PATH
self.dispatch('MSGSENT', (jid, msg, keyID))
- return msg_id
+ if callback:
+ callback(msg_id, *callback_args)
def send_stanza(self, stanza):
''' send a stanza untouched '''
Modified: trunk/src/common/connection_handlers.py
===================================================================
--- trunk/src/common/connection_handlers.py 2009-02-03 15:10:56 UTC (rev 11054)
+++ trunk/src/common/connection_handlers.py 2009-02-06 19:01:36 UTC (rev 11055)
@@ -1772,6 +1772,7 @@
encrypted = False
xep_200_encrypted = msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO)
+ session = None
if mtype != 'groupchat':
session = self.get_or_create_session(frm, thread_id)
@@ -1853,10 +1854,22 @@
keyID = gajim.config.get_per('accounts', self.name, 'keyid')
if keyID:
- decmsg = self.gpg.decrypt(encmsg, keyID)
- # \x00 chars are not allowed in C (so in GTK)
- msgtxt = decmsg.replace('\x00', '')
- encrypted = 'xep27'
+ def decrypt_thread(encmsg, keyID):
+ decmsg = self.gpg.decrypt(encmsg, keyID)
+ # \x00 chars are not allowed in C (so in GTK)
+ msgtxt = decmsg.replace('\x00', '')
+ encrypted = 'xep27'
+ return (msgtxt, encrypted)
+ gajim.thread_interface(decrypt_thread, [encmsg, keyID],
+ self._on_message_decrypted, [mtype, msg, session, frm, jid,
+ invite, tim])
+ return
+ self._on_message_decrypted((msgtxt, encrypted), mtype, msg, session, frm,
+ jid, invite, tim)
+
+ def _on_message_decrypted(self, output, mtype, msg, session, frm, jid,
+ invite, tim):
+ msgtxt, encrypted = output
if mtype == 'error':
self.dispatch_error_message(msg, msgtxt, session, frm, tim)
elif mtype == 'groupchat':
Modified: trunk/src/common/gajim.py
===================================================================
--- trunk/src/common/gajim.py 2009-02-03 15:10:56 UTC (rev 11054)
+++ trunk/src/common/gajim.py 2009-02-06 19:01:36 UTC (rev 11055)
@@ -63,6 +63,7 @@
sys.exit(1)
interface = None # The actual interface (the gtk one for the moment)
+thread_interface = None # Interface to run a thread and then a callback
config = config.Config()
version = config.get('version')
connections = {} # 'account name': 'account (connection.Connection) instance'
Modified: trunk/src/common/xmpp/dispatcher_nb.py
===================================================================
--- trunk/src/common/xmpp/dispatcher_nb.py 2009-02-03 15:10:56 UTC (rev 11054)
+++ trunk/src/common/xmpp/dispatcher_nb.py 2009-02-06 19:01:36 UTC (rev 11055)
@@ -134,7 +134,10 @@
self._owner.lastErrNode = None
self._owner.lastErr = None
self._owner.lastErrCode = None
- self.StreamInit()
+ if hasattr(self._owner, 'StreamInit'):
+ self._owner.StreamInit()
+ else:
+ self.StreamInit()
def plugout(self):
''' Prepares instance to be destructed. '''
Modified: trunk/src/common/zeroconf/client_zeroconf.py
===================================================================
--- trunk/src/common/zeroconf/client_zeroconf.py 2009-02-03 15:10:56 UTC (rev 11054)
+++ trunk/src/common/zeroconf/client_zeroconf.py 2009-02-06 19:01:36 UTC (rev 11055)
@@ -155,11 +155,16 @@
if on_not_ok:
on_not_ok('Connection to host could not be established.')
return
- id = stanza.getThread()
+ thread_id = stanza.getThread()
+ id_ = stanza.getID()
+ if not id_:
+ id_ = self.Dispatcher.getAnID()
if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd):
- self.conn_holder.ids_of_awaiting_messages[self.fd].append(id)
+ self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_,
+ thread_id))
else:
- self.conn_holder.ids_of_awaiting_messages[self.fd] = [id]
+ self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_,
+ thread_id)]
def add_stanza(self, stanza, is_message=False):
if self.Connection:
@@ -170,24 +175,32 @@
self.stanzaqueue.append((stanza, is_message))
if is_message:
- id = stanza.getThread()
+ id_ = stanza.getID()
+ if not id_:
+ id_ = self.Dispatcher.getAnID()
if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd):
- self.conn_holder.ids_of_awaiting_messages[self.fd].append(id)
+ self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_,
+ thread_id))
else:
- self.conn_holder.ids_of_awaiting_messages[self.fd] = [id]
+ self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_,
+ thread_id)]
return True
def on_message_sent(self, connection_id):
- self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0)
+ id_, thread_id = \
+ self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0)
+ if self.on_ok:
+ self.on_ok(id_)
+ # use on_ok only on first message. For others it's called in
+ # ClientZeroconf
+ self.on_ok = None
def on_connect(self, conn):
self.Connection = conn
self.Connection.PlugIn(self)
dispatcher_nb.Dispatcher().PlugIn(self)
self._register_handlers()
- if self.on_ok:
- self.on_ok()
def StreamInit(self):
''' Send an initial stream header. '''
@@ -393,8 +406,9 @@
def read_timeout(self):
ids = self.client.conn_holder.ids_of_awaiting_messages
if self.fd in ids and len(ids[self.fd]) > 0:
- for id in ids[self.fd]:
- self._owner.Dispatcher.Event('', DATA_ERROR, (self.client.to, id))
+ for (id_, thread_id) in ids[self.fd]:
+ self._owner.Dispatcher.Event('', DATA_ERROR, (self.client.to,
+ thread_id))
ids[self.fd] = []
self.pollend()
@@ -679,8 +693,7 @@
stanza.setID(id_)
if conn.add_stanza(stanza, is_message):
if on_ok:
- on_ok()
- return id_
+ on_ok(id_)
if item['address'] in self.ip_to_hash:
hash_ = self.ip_to_hash[item['address']]
@@ -690,14 +703,11 @@
stanza.setID(id_)
if conn.add_stanza(stanza, is_message):
if on_ok:
- on_ok()
- return id_
+ on_ok(id_)
# otherwise open new connection
stanza.setID('zero')
P2PClient(None, item['address'], item['port'], self,
[(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok)
- return 'zero'
-
# vim: se ts=3:
Modified: trunk/src/common/zeroconf/connection_zeroconf.py
===================================================================
--- trunk/src/common/zeroconf/connection_zeroconf.py 2009-02-03 15:10:56 UTC (rev 11054)
+++ trunk/src/common/zeroconf/connection_zeroconf.py 2009-02-06 19:01:36 UTC (rev 11055)
@@ -364,7 +364,7 @@
def send_message(self, jid, msg, keyID, type_='chat', subject='',
chatstate=None, msg_id=None, composing_xep=None, resource=None,
user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
- original_message=None, delayed=None):
+ original_message=None, delayed=None, callback=None, callback_args=[]):
fjid = jid
if msg and not xhtml and gajim.config.get(
@@ -402,7 +402,7 @@
# Encryption failed, do not send message
tim = time.localtime()
self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session))
- return 3
+ return
if type_ == 'chat':
msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_,
@@ -458,7 +458,7 @@
if session.enable_encryption:
msg_iq = session.encrypt_stanza(msg_iq)
- def on_send_ok():
+ def on_send_ok(id):
no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
ji = gajim.get_jid_without_resource(jid)
if session.is_loggable() and self.name not in no_log_for and\
@@ -473,9 +473,12 @@
else:
kind = 'single_msg_sent'
gajim.logger.write(kind, jid, log_msg)
-
+
self.dispatch('MSGSENT', (jid, msg, keyID))
+ if callback:
+ callback(id, *callback_args)
+
def on_send_not_ok(reason):
reason += ' ' + _('Your message could not be sent.')
self.dispatch('MSGERROR', [jid, -1, reason, None, None, session])
@@ -484,8 +487,6 @@
if ret == -1:
# Contact Offline
self.dispatch('MSGERROR', [jid, -1, _('Contact is offline. Your message could not be sent.'), None, None, session])
- return ret
- return ret
def send_stanza(self, stanza):
Modified: trunk/src/gajim.py
===================================================================
--- trunk/src/gajim.py 2009-02-03 15:10:56 UTC (rev 11054)
+++ trunk/src/gajim.py 2009-02-06 19:01:36 UTC (rev 11055)
@@ -423,6 +423,7 @@
import roster_window
import profile_window
import config
+from threading import Thread
class PassphraseRequest:
@@ -482,6 +483,22 @@
cancel_handler=_cancel)
self.dialog_created = True
+
+class ThreadInterface:
+ def __init__(self, func, func_args, callback, callback_args):
+ '''Call a function in a thread
+
+ :param func: the function to call in the thread
+ :param func_args: list or arguments for this function
+ :param callback: callback to call once function is finished
+ :param callback_args: list of arguments for this callback
+ '''
+ def thread_function(func, func_args, callback, callback_args):
+ output = func(*func_args)
+ gobject.idle_add(callback, output, *callback_args)
+ Thread(target=thread_function, args=(func, func_args, callback,
+ callback_args)).start()
+
class Interface:
################################################################################
@@ -3071,6 +3088,7 @@
def __init__(self):
gajim.interface = self
+ gajim.thread_interface = ThreadInterface
# This is the manager and factory of message windows set by the module
self.msg_win_mgr = None
self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
Modified: trunk/src/message_control.py
===================================================================
--- trunk/src/message_control.py 2009-02-03 15:10:56 UTC (rev 11054)
+++ trunk/src/message_control.py 2009-02-06 19:01:36 UTC (rev 11055)
@@ -158,9 +158,9 @@
if crypto_changed:
self.print_esession_details()
- def send_message(self, message, keyID = '', type_ = 'chat',
- chatstate = None, msg_id = None, composing_xep = None, resource = None,
- user_nick = None, xhtml = None):
+ def send_message(self, message, keyID='', type_='chat', chatstate=None,
+ msg_id=None, composing_xep=None, resource=None, user_nick=None, xhtml=None,
+ callback=None, callback_args=[]):
# Send the given message to the active tab.
# Doesn't return None if error
jid = self.contact.jid
@@ -182,11 +182,10 @@
self.set_session(sess)
# Send and update history
- return conn.send_message(jid, message, keyID, type_ = type_,
- chatstate = chatstate, msg_id = msg_id,
- composing_xep = composing_xep,
- resource = self.resource, user_nick = user_nick,
- session = self.session,
- original_message = original_message, xhtml = xhtml)
+ conn.send_message(jid, message, keyID, type_=type_, chatstate=chatstate,
+ msg_id=msg_id, composing_xep=composing_xep, resource=self.resource,
+ user_nick=user_nick, session=self.session,
+ original_message=original_message, xhtml=xhtml, callback=callback,
+ callback_args=callback_args)
# vim: se ts=3: