handle outgoing messages with events. Fixes #6743
This commit is contained in:
parent
9fc82bbffc
commit
4ac1768040
|
@ -54,6 +54,7 @@ from common.pep import MOODS, ACTIVITIES
|
||||||
from common.xmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
|
from common.xmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
|
||||||
from common.xmpp.protocol import NS_RECEIPTS, NS_ESESSION
|
from common.xmpp.protocol import NS_RECEIPTS, NS_ESESSION
|
||||||
from common.xmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_ICE_UDP
|
from common.xmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_ICE_UDP
|
||||||
|
from common.connection_handlers_events import MessageOutgoingEvent
|
||||||
|
|
||||||
from command_system.implementation.middleware import ChatCommandProcessor
|
from command_system.implementation.middleware import ChatCommandProcessor
|
||||||
from command_system.implementation.middleware import CommandTools
|
from command_system.implementation.middleware import CommandTools
|
||||||
|
@ -517,6 +518,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
||||||
menu.show_all()
|
menu.show_all()
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
|
super(ChatControlBase, self).shutdown()
|
||||||
# PluginSystem: removing GUI extension points connected with ChatControlBase
|
# PluginSystem: removing GUI extension points connected with ChatControlBase
|
||||||
# instance object
|
# instance object
|
||||||
gajim.plugin_manager.remove_gui_extension_point('chat_control_base', self)
|
gajim.plugin_manager.remove_gui_extension_point('chat_control_base', self)
|
||||||
|
@ -848,8 +850,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
||||||
return label
|
return label
|
||||||
|
|
||||||
def send_message(self, message, keyID='', type_='chat', chatstate=None,
|
def send_message(self, message, keyID='', type_='chat', chatstate=None,
|
||||||
msg_id=None, composing_xep=None, resource=None, xhtml=None,
|
msg_id=None, composing_xep=None, resource=None, xhtml=None, callback=None,
|
||||||
callback=None, callback_args=[], process_commands=True):
|
callback_args=[], process_commands=True):
|
||||||
"""
|
"""
|
||||||
Send the given message to the active tab. Doesn't return None if error
|
Send the given message to the active tab. Doesn't return None if error
|
||||||
"""
|
"""
|
||||||
|
@ -860,11 +862,12 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
||||||
return
|
return
|
||||||
|
|
||||||
label = self.get_seclabel()
|
label = self.get_seclabel()
|
||||||
MessageControl.send_message(self, message, keyID, type_=type_,
|
|
||||||
|
gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
|
||||||
|
account=self.account, message=message, keyID=keyID, type_=type_,
|
||||||
chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
|
chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
|
||||||
resource=resource, user_nick=self.user_nick, xhtml=xhtml,
|
resource=resource, user_nick=self.user_nick, xhtml=xhtml,
|
||||||
label=label,
|
label=label, callback=callback, callback_args= callback_args))
|
||||||
callback=callback, callback_args=callback_args)
|
|
||||||
|
|
||||||
# Record the history of sent messages
|
# Record the history of sent messages
|
||||||
self.save_message(message, 'sent')
|
self.save_message(message, 'sent')
|
||||||
|
@ -2209,6 +2212,7 @@ class ChatControl(ChatControlBase):
|
||||||
"""
|
"""
|
||||||
Send a message to contact
|
Send a message to contact
|
||||||
"""
|
"""
|
||||||
|
message = helpers.remove_invalid_xml_chars(message)
|
||||||
if message in ('', None, '\n'):
|
if message in ('', None, '\n'):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -2624,12 +2628,14 @@ class ChatControl(ChatControlBase):
|
||||||
# if we're inactive prevent composing (JEP violation)
|
# if we're inactive prevent composing (JEP violation)
|
||||||
if contact.our_chatstate == 'inactive' and state == 'composing':
|
if contact.our_chatstate == 'inactive' and state == 'composing':
|
||||||
# go active before
|
# go active before
|
||||||
MessageControl.send_message(self, None, chatstate='active')
|
gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
|
||||||
|
account=self.account, chatstate='active'))
|
||||||
contact.our_chatstate = 'active'
|
contact.our_chatstate = 'active'
|
||||||
self.reset_kbd_mouse_timeout_vars()
|
self.reset_kbd_mouse_timeout_vars()
|
||||||
|
|
||||||
MessageControl.send_message(self, "", chatstate = state,
|
gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
|
||||||
msg_id = contact.msg_id, composing_xep = contact.composing_xep)
|
account=self.account, chatstate=state, msg_id=contact.msg_id,
|
||||||
|
composing_xep=contact.composing_xep))
|
||||||
|
|
||||||
contact.our_chatstate = state
|
contact.our_chatstate = state
|
||||||
if contact.our_chatstate == 'active':
|
if contact.our_chatstate == 'active':
|
||||||
|
|
|
@ -717,6 +717,8 @@ class Connection(CommonConnection, ConnectionHandlers):
|
||||||
self._nec_agent_info_error_received)
|
self._nec_agent_info_error_received)
|
||||||
gajim.ged.register_event_handler('agent-info-received', ged.CORE,
|
gajim.ged.register_event_handler('agent-info-received', ged.CORE,
|
||||||
self._nec_agent_info_received)
|
self._nec_agent_info_received)
|
||||||
|
gajim.ged.register_event_handler('message-outgoing', ged.OUT_CORE,
|
||||||
|
self._nec_message_outgoing)
|
||||||
# END __init__
|
# END __init__
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
|
@ -727,6 +729,8 @@ class Connection(CommonConnection, ConnectionHandlers):
|
||||||
self._nec_agent_info_error_received)
|
self._nec_agent_info_error_received)
|
||||||
gajim.ged.remove_event_handler('agent-info-received', ged.CORE,
|
gajim.ged.remove_event_handler('agent-info-received', ged.CORE,
|
||||||
self._nec_agent_info_received)
|
self._nec_agent_info_received)
|
||||||
|
gajim.ged.remove_event_handler('message-outgoing', ged.OUT_CORE,
|
||||||
|
self._nec_message_outgoing)
|
||||||
|
|
||||||
def get_config_values_or_default(self):
|
def get_config_values_or_default(self):
|
||||||
if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
|
if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
|
||||||
|
@ -1764,6 +1768,30 @@ class Connection(CommonConnection, ConnectionHandlers):
|
||||||
session=session, forward_from=forward_from, form_node=form_node,
|
session=session, forward_from=forward_from, form_node=form_node,
|
||||||
original_message=original_message, delayed=delayed, callback=cb)
|
original_message=original_message, delayed=delayed, callback=cb)
|
||||||
|
|
||||||
|
def _nec_message_outgoing(self, obj):
|
||||||
|
if obj.account != self.name:
|
||||||
|
return
|
||||||
|
|
||||||
|
def cb(jid, msg, keyID, forward_from, session, original_message,
|
||||||
|
subject, type_, msg_iq):
|
||||||
|
msg_id = self.connection.send(msg_iq, now=obj.now)
|
||||||
|
jid = helpers.parse_jid(obj.jid)
|
||||||
|
gajim.nec.push_incoming_event(MessageSentEvent(None, conn=self,
|
||||||
|
jid=jid, message=msg, keyID=keyID, chatstate=obj.chatstate))
|
||||||
|
if obj.callback:
|
||||||
|
obj.callback(msg_id, *obj.callback_args)
|
||||||
|
|
||||||
|
self.log_message(jid, msg, forward_from, session, original_message,
|
||||||
|
subject, type_)
|
||||||
|
|
||||||
|
self._prepare_message(obj.jid, obj.message, obj.keyID, type_=obj.type_,
|
||||||
|
subject=obj.subject, chatstate=obj.chatstate, msg_id=obj.msg_id,
|
||||||
|
composing_xep=obj.composing_xep, resource=obj.resource,
|
||||||
|
user_nick=obj.user_nick, xhtml=obj.xhtml, label=obj.label,
|
||||||
|
session=obj.session, forward_from=obj.forward_from,
|
||||||
|
form_node=obj.form_node, original_message=obj.original_message,
|
||||||
|
delayed=obj.delayed, callback=cb)
|
||||||
|
|
||||||
def send_contacts(self, contacts, jid):
|
def send_contacts(self, contacts, jid):
|
||||||
"""
|
"""
|
||||||
Send contacts with RosterX (Xep-0144)
|
Send contacts with RosterX (Xep-0144)
|
||||||
|
|
|
@ -2042,3 +2042,31 @@ class NotificationEvent(nec.NetworkIncomingEvent):
|
||||||
elif self.notif_type == 'pres':
|
elif self.notif_type == 'pres':
|
||||||
self.handle_incoming_pres_event(self.base_event)
|
self.handle_incoming_pres_event(self.base_event)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
class MessageOutgoingEvent(nec.NetworkIncomingEvent):
|
||||||
|
name = 'message-outgoing'
|
||||||
|
base_network_events = []
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.message = ''
|
||||||
|
self.keyID = None
|
||||||
|
self.type_ = 'chat'
|
||||||
|
self.subject = ''
|
||||||
|
self.chatstate = None
|
||||||
|
self.msg_id = None
|
||||||
|
self.composing_xep = None
|
||||||
|
self.resource = None
|
||||||
|
self.user_nick = None
|
||||||
|
self.xhtml = None
|
||||||
|
self.label = None
|
||||||
|
self.session = None
|
||||||
|
self.forward_from = None
|
||||||
|
self.form_node = None
|
||||||
|
self.original_message = ''
|
||||||
|
self.delayed = None
|
||||||
|
self.callback = None
|
||||||
|
self.callback_args = []
|
||||||
|
self.now = False
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
return True
|
|
@ -21,6 +21,7 @@ Global Events Dispatcher module.
|
||||||
:author: Mateusz Biliński <mateusz@bilinski.it>
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
:since: 8th August 2008
|
:since: 8th August 2008
|
||||||
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:copyright: Copyright (2011) Yann Leboulanger <asterix@lagaule.org>
|
||||||
:license: GPL
|
:license: GPL
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -39,6 +40,18 @@ GUI2 = 90
|
||||||
POSTGUI2 = 100
|
POSTGUI2 = 100
|
||||||
POSTGUI = 110
|
POSTGUI = 110
|
||||||
|
|
||||||
|
OUT_PREGUI = 10
|
||||||
|
OUT_PREGUI1 = 20
|
||||||
|
OUT_GUI1 = 30
|
||||||
|
OUT_POSTGUI1 = 40
|
||||||
|
OUT_PREGUI2 = 50
|
||||||
|
OUT_GUI2 = 60
|
||||||
|
OUT_POSTGUI2 = 70
|
||||||
|
OUT_POSTGUI = 80
|
||||||
|
OUT_PRECORE = 90
|
||||||
|
OUT_CORE = 100
|
||||||
|
OUT_POSTCORE = 110
|
||||||
|
|
||||||
class GlobalEventsDispatcher(object):
|
class GlobalEventsDispatcher(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -21,6 +21,7 @@ Network Events Controller.
|
||||||
:author: Mateusz Biliński <mateusz@bilinski.it>
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
:since: 10th August 2008
|
:since: 10th August 2008
|
||||||
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:copyright: Copyright (2011) Yann Leboulanger <asterix@lagaule.org>
|
||||||
:license: GPL
|
:license: GPL
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -38,23 +39,38 @@ class NetworkEventsController(object):
|
||||||
Values: list of class objects that are subclasses
|
Values: list of class objects that are subclasses
|
||||||
of `NetworkIncomingEvent`
|
of `NetworkIncomingEvent`
|
||||||
'''
|
'''
|
||||||
|
self.outgoing_events_generators = {}
|
||||||
|
'''
|
||||||
|
Keys: names of events
|
||||||
|
Values: list of class objects that are subclasses
|
||||||
|
of `NetworkOutgoingEvent`
|
||||||
|
'''
|
||||||
|
|
||||||
def register_incoming_event(self, event_class):
|
def register_incoming_event(self, event_class):
|
||||||
for base_event_name in event_class.base_network_events:
|
for base_event_name in event_class.base_network_events:
|
||||||
event_list = self.incoming_events_generators.setdefault(base_event_name, [])
|
event_list = self.incoming_events_generators.setdefault(
|
||||||
|
base_event_name, [])
|
||||||
if not event_class in event_list:
|
if not event_class in event_list:
|
||||||
event_list.append(event_class)
|
event_list.append(event_class)
|
||||||
|
|
||||||
def unregister_incoming_event(self, event_class):
|
def unregister_incoming_event(self, event_class):
|
||||||
for base_event_name in event_class.base_network_events:
|
for base_event_name in event_class.base_network_events:
|
||||||
if base_event_name in self.incoming_events_generators:
|
if base_event_name in self.incoming_events_generators:
|
||||||
self.incoming_events_generators[base_event_name].remove(event_class)
|
self.incoming_events_generators[base_event_name].remove(
|
||||||
|
event_class)
|
||||||
|
|
||||||
def register_outgoing_event(self, event_class):
|
def register_outgoing_event(self, event_class):
|
||||||
pass
|
for base_event_name in event_class.base_network_events:
|
||||||
|
event_list = self.outgoing_events_generators.setdefault(
|
||||||
|
base_event_name, [])
|
||||||
|
if not event_class in event_list:
|
||||||
|
event_list.append(event_class)
|
||||||
|
|
||||||
def unregister_outgoing_event(self, event_class):
|
def unregister_outgoing_event(self, event_class):
|
||||||
pass
|
for base_event_name in event_class.base_network_events:
|
||||||
|
if base_event_name in self.outgoing_events_generators:
|
||||||
|
self.outgoing_events_generators[base_event_name].remove(
|
||||||
|
event_class)
|
||||||
|
|
||||||
def push_incoming_event(self, event_object):
|
def push_incoming_event(self, event_object):
|
||||||
if event_object.generate():
|
if event_object.generate():
|
||||||
|
@ -62,7 +78,9 @@ class NetworkEventsController(object):
|
||||||
self._generate_events_based_on_incoming_event(event_object)
|
self._generate_events_based_on_incoming_event(event_object)
|
||||||
|
|
||||||
def push_outgoing_event(self, event_object):
|
def push_outgoing_event(self, event_object):
|
||||||
pass
|
if event_object.generate():
|
||||||
|
if not gajim.ged.raise_event(event_object.name, event_object):
|
||||||
|
self._generate_events_based_on_outgoing_event(event_object)
|
||||||
|
|
||||||
def _generate_events_based_on_incoming_event(self, event_object):
|
def _generate_events_based_on_incoming_event(self, event_object):
|
||||||
'''
|
'''
|
||||||
|
@ -75,11 +93,36 @@ class NetworkEventsController(object):
|
||||||
'''
|
'''
|
||||||
base_event_name = event_object.name
|
base_event_name = event_object.name
|
||||||
if base_event_name in self.incoming_events_generators:
|
if base_event_name in self.incoming_events_generators:
|
||||||
for new_event_class in self.incoming_events_generators[base_event_name]:
|
for new_event_class in self.incoming_events_generators[
|
||||||
new_event_object = new_event_class(None, base_event=event_object)
|
base_event_name]:
|
||||||
|
new_event_object = new_event_class(None,
|
||||||
|
base_event=event_object)
|
||||||
if new_event_object.generate():
|
if new_event_object.generate():
|
||||||
if not gajim.ged.raise_event(new_event_object.name, new_event_object):
|
if not gajim.ged.raise_event(new_event_object.name,
|
||||||
self._generate_events_based_on_incoming_event(new_event_object)
|
new_event_object):
|
||||||
|
self._generate_events_based_on_incoming_event(
|
||||||
|
new_event_object)
|
||||||
|
|
||||||
|
def _generate_events_based_on_outgoing_event(self, event_object):
|
||||||
|
'''
|
||||||
|
:return: True if even_object should be dispatched through Global
|
||||||
|
Events Dispatcher, False otherwise. This can be used to replace
|
||||||
|
base events with those that more data computed (easier to use
|
||||||
|
by handlers).
|
||||||
|
:note: replacing mechanism is not implemented currently, but will be
|
||||||
|
based on attribute in new network events object.
|
||||||
|
'''
|
||||||
|
base_event_name = event_object.name
|
||||||
|
if base_event_name in self.outgoing_events_generators:
|
||||||
|
for new_event_class in self.outgoing_events_generators[
|
||||||
|
base_event_name]:
|
||||||
|
new_event_object = new_event_class(None,
|
||||||
|
base_event=event_object)
|
||||||
|
if new_event_object.generate():
|
||||||
|
if not gajim.ged.raise_event(new_event_object.name,
|
||||||
|
new_event_object):
|
||||||
|
self._generate_events_based_on_outgoing_event(
|
||||||
|
new_event_object)
|
||||||
|
|
||||||
class NetworkEvent(object):
|
class NetworkEvent(object):
|
||||||
name = ''
|
name = ''
|
||||||
|
@ -88,10 +131,10 @@ class NetworkEvent(object):
|
||||||
if new_name:
|
if new_name:
|
||||||
self.name = new_name
|
self.name = new_name
|
||||||
|
|
||||||
self._set_kwargs_as_attributes(**kwargs)
|
|
||||||
|
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
|
self._set_kwargs_as_attributes(**kwargs)
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -130,5 +173,7 @@ class NetworkIncomingEvent(NetworkEvent):
|
||||||
|
|
||||||
|
|
||||||
class NetworkOutgoingEvent(NetworkEvent):
|
class NetworkOutgoingEvent(NetworkEvent):
|
||||||
pass
|
base_network_events = []
|
||||||
|
'''
|
||||||
|
Names of base network events that new event is going to be generated on.
|
||||||
|
'''
|
|
@ -30,6 +30,7 @@ import gtkgui_helpers
|
||||||
|
|
||||||
from common import gajim
|
from common import gajim
|
||||||
from common import helpers
|
from common import helpers
|
||||||
|
from common import ged
|
||||||
from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
|
from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
|
||||||
|
|
||||||
# Derived types MUST register their type IDs here if custom behavor is required
|
# Derived types MUST register their type IDs here if custom behavor is required
|
||||||
|
@ -64,6 +65,9 @@ class MessageControl(object):
|
||||||
self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % widget_name)
|
self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % widget_name)
|
||||||
self.widget = self.xml.get_object('%s_hbox' % widget_name)
|
self.widget = self.xml.get_object('%s_hbox' % widget_name)
|
||||||
|
|
||||||
|
gajim.ged.register_event_handler('message-outgoing', ged.OUT_GUI1,
|
||||||
|
self._nec_message_outgoing)
|
||||||
|
|
||||||
def get_full_jid(self):
|
def get_full_jid(self):
|
||||||
fjid = self.contact.jid
|
fjid = self.contact.jid
|
||||||
if self.resource:
|
if self.resource:
|
||||||
|
@ -110,7 +114,8 @@ class MessageControl(object):
|
||||||
"""
|
"""
|
||||||
Derived classes MUST implement this
|
Derived classes MUST implement this
|
||||||
"""
|
"""
|
||||||
pass
|
gajim.ged.remove_event_handler('message-outgoing', ged.OUT_GUI1,
|
||||||
|
self._nec_message_outgoing)
|
||||||
|
|
||||||
def repaint_themed_widgets(self):
|
def repaint_themed_widgets(self):
|
||||||
"""
|
"""
|
||||||
|
@ -214,40 +219,35 @@ class MessageControl(object):
|
||||||
if crypto_changed or archiving_changed:
|
if crypto_changed or archiving_changed:
|
||||||
self.print_session_details()
|
self.print_session_details()
|
||||||
|
|
||||||
def send_message(self, message, keyID='', type_='chat', chatstate=None,
|
def _nec_message_outgoing(self, obj):
|
||||||
msg_id=None, composing_xep=None, resource=None, user_nick=None,
|
|
||||||
xhtml=None, label=None, callback=None, callback_args=[]):
|
|
||||||
# Send the given message to the active tab.
|
# Send the given message to the active tab.
|
||||||
# Doesn't return None if error
|
# Doesn't return None if error
|
||||||
jid = self.contact.jid
|
if obj.account != self.account:
|
||||||
|
return
|
||||||
|
|
||||||
message = helpers.remove_invalid_xml_chars(message)
|
obj.jid = self.contact.jid
|
||||||
|
obj.message = helpers.remove_invalid_xml_chars(obj.message)
|
||||||
|
obj.original_message = obj.message
|
||||||
|
|
||||||
original_message = message
|
|
||||||
conn = gajim.connections[self.account]
|
conn = gajim.connections[self.account]
|
||||||
|
|
||||||
if not self.session:
|
if not self.session:
|
||||||
if not resource:
|
if not obj.resource:
|
||||||
if self.resource:
|
if self.resource:
|
||||||
resource = self.resource
|
obj.resource = self.resource
|
||||||
else:
|
else:
|
||||||
resource = self.contact.resource
|
obj.resource = self.contact.resource
|
||||||
sess = conn.find_controlless_session(jid, resource=resource)
|
sess = conn.find_controlless_session(obj.jid, resource=obj.resource)
|
||||||
|
|
||||||
if self.resource:
|
if self.resource:
|
||||||
jid += '/' + self.resource
|
obj.jid += '/' + self.resource
|
||||||
|
|
||||||
if not sess:
|
if not sess:
|
||||||
if self.type_id == TYPE_PM:
|
if self.type_id == TYPE_PM:
|
||||||
sess = conn.make_new_session(jid, type_='pm')
|
sess = conn.make_new_session(obj.jid, type_='pm')
|
||||||
else:
|
else:
|
||||||
sess = conn.make_new_session(jid)
|
sess = conn.make_new_session(obj.jid)
|
||||||
|
|
||||||
self.set_session(sess)
|
self.set_session(sess)
|
||||||
|
|
||||||
# Send and update history
|
obj.session = self.session
|
||||||
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, label=label, callback=callback,
|
|
||||||
callback_args=callback_args)
|
|
||||||
|
|
Loading…
Reference in New Issue