Commit 3a3be94a authored by Bronko's avatar Bronko Committed by Philipp Hörist

integrate httpupload plugin into gajim core

add new config option 'filetransfer_preference'
add drag and drop support for file upload
parent c23af9c9
......@@ -97,6 +97,10 @@ class ChatControl(ChatControlBase):
self._on_authentication_button_clicked)
self.handlers[id_] = self.authentication_button
self.sendfile_button = self.xml.get_object('sendfile_button')
self.sendfile_button.set_action_name('win.send-file-' + \
self.control_id)
# Add lock image to show chat encryption
self.lock_image = self.xml.get_object('lock_image')
......@@ -242,7 +246,6 @@ class ChatControl(ChatControlBase):
def add_actions(self):
actions = [
('send-file-', self._on_send_file),
('invite-contacts-', self._on_invite_contacts),
('add-to-roster-', self._on_add_to_roster),
('information-', self._on_information),
......@@ -288,16 +291,42 @@ class ChatControl(ChatControlBase):
win.lookup_action('toggle-video-' + self.control_id).set_enabled(
online and self.video_available)
# Send file (HTTP File Upload)
httpupload = win.lookup_action(
'send-file-httpupload-' + self.control_id)
httpupload.set_enabled(
online and app.connections[self.account].httpupload)
# Send file (Jingle)
jingle_conditions = (
(self.contact.supports(NS_FILE) or
self.contact.supports(NS_JINGLE_FILE_TRANSFER_5)) and
self.contact.show != 'offline')
jingle = win.lookup_action('send-file-jingle-' + self.control_id)
jingle.set_enabled(online and jingle_conditions)
# Send file
if ((self.contact.supports(NS_FILE) or \
self.contact.supports(NS_JINGLE_FILE_TRANSFER_5)) and \
(self.type_id == 'chat' or self.gc_contact.resource)) and \
self.contact.show != 'offline' and online:
win.lookup_action('send-file-' + self.control_id).set_enabled(
True)
else:
win.lookup_action('send-file-' + self.control_id).set_enabled(
False)
win.lookup_action(
'send-file-' + self.control_id).set_enabled(
jingle.get_enabled() or httpupload.get_enabled())
# Set File Transfer Button tooltip
ft_pref = app.config.get_per('accounts', self.account,
'filetransfer_preference')
tooltip_text = None
if httpupload.get_enabled() and jingle.get_enabled():
if ft_pref == 'httpupload':
tooltip_text = _('HTTP File Upload')
else:
tooltip_text = _('Jingle File Transfer')
elif httpupload.get_enabled():
tooltip_text = _('HTTP File Upload')
elif jingle.get_enabled():
tooltip_text = _('Jingle File Transfer')
elif online:
tooltip_text = _('No File Transfer available')
self.sendfile_button.set_tooltip_text(tooltip_text)
# Convert to GC
if app.config.get_per('accounts', self.account, 'is_zeroconf'):
......@@ -315,9 +344,6 @@ class ChatControl(ChatControlBase):
win.lookup_action(
'information-' + self.control_id).set_enabled(online)
def _on_send_file(self, action, param):
super()._on_send_file()
def _on_add_to_roster(self, action, param):
dialogs.AddNewContactWindow(self.account, self.contact.jid)
......@@ -1265,43 +1291,37 @@ class ChatControl(ChatControlBase):
self.show_avatar()
def _on_drag_data_received(self, widget, context, x, y, selection,
target_type, timestamp):
target_type, timestamp):
if not selection.get_data():
return
# get contact info (check for PM = private chat)
if self.TYPE_ID == message_control.TYPE_PM:
c = self.gc_contact
c = self.gc_contact.as_contact()
else:
c = self.contact
if target_type == self.TARGET_TYPE_URI_LIST:
if not c.resource: # If no resource is known, we can't send a file
# file drag and drop (handled in chat_control_base)
self.drag_data_file_transfer(c, selection, self)
else:
# chat2muc
treeview = app.interface.roster.tree
model = treeview.get_model()
data = selection.get_data()
path = treeview.get_selection().get_selected_rows()[1][0]
iter_ = model.get_iter(path)
type_ = model[iter_][2]
if type_ != 'contact': # source is not a contact
return
dropped_jid = data
# we may have more than one file dropped
uri_splitted = selection.get_uris()
for uri in uri_splitted:
path = helpers.get_file_path_from_dnd_dropped_uri(uri)
if os.path.isfile(path): # is it file?
ft = app.interface.instances['file_transfers']
ft.send_file(self.account, c, path)
return
# chat2muc
treeview = app.interface.roster.tree
model = treeview.get_model()
data = selection.get_data()
path = treeview.get_selection().get_selected_rows()[1][0]
iter_ = model.get_iter(path)
type_ = model[iter_][2]
if type_ != 'contact': # source is not a contact
return
dropped_jid = data
dropped_transport = app.get_transport_name_from_jid(dropped_jid)
c_transport = app.get_transport_name_from_jid(c.jid)
if dropped_transport or c_transport:
return # transport contacts cannot be invited
dropped_transport = app.get_transport_name_from_jid(dropped_jid)
c_transport = app.get_transport_name_from_jid(c.jid)
if dropped_transport or c_transport:
return # transport contacts cannot be invited
dialogs.TransformChatToMUC(self.account, [c.jid], [dropped_jid])
dialogs.TransformChatToMUC(self.account, [c.jid], [dropped_jid])
def _on_message_tv_buffer_changed(self, textbuffer):
super()._on_message_tv_buffer_changed(textbuffer)
......
......@@ -27,6 +27,7 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
import os
import time
from gi.repository import Gtk
from gi.repository import Gdk
......@@ -403,6 +404,24 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
action.connect('activate', self._on_history)
self.parent_win.window.add_action(action)
action = Gio.SimpleAction.new(
'send-file-%s' % self.control_id, None)
action.connect('activate', self._on_send_file)
action.set_enabled(False)
self.parent_win.window.add_action(action)
action = Gio.SimpleAction.new(
'send-file-httpupload-%s' % self.control_id, None)
action.connect('activate', self._on_send_httpupload)
action.set_enabled(False)
self.parent_win.window.add_action(action)
action = Gio.SimpleAction.new(
'send-file-jingle-%s' % self.control_id, None)
action.connect('activate', self._on_send_jingle)
action.set_enabled(False)
self.parent_win.window.add_action(action)
# Actions
def _on_history(self, action, param):
......@@ -730,6 +749,44 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.drag_entered_conv = True
self.conv_textview.tv.set_editable(True)
def drag_data_file_transfer(self, contact, selection, widget):
# get file transfer preference
ft_pref = app.config.get_per('accounts', self.account,
'filetransfer_preference')
win = self.parent_win.window
con = app.connections[self.account]
httpupload = win.lookup_action(
'send-file-httpupload-%s' % self.control_id)
jingle = win.lookup_action('send-file-jingle-%s' % self.control_id)
# we may have more than one file dropped
uri_splitted = selection.get_uris()
for uri in uri_splitted:
path = helpers.get_file_path_from_dnd_dropped_uri(uri)
if not os.path.isfile(path): # is it a file?
continue
if self.type_id == message_control.TYPE_GC:
# groupchat only supports httpupload on drag and drop
if httpupload.get_enabled():
# use httpupload
con.check_file_before_transfer(
path, self.encryption, contact,
self.session, groupchat=True)
else:
if httpupload.get_enabled() and jingle.get_enabled():
if ft_pref == 'httpupload':
con.check_file_before_transfer(
path, self.encryption, contact, self.session)
else:
ft = app.interface.instances['file_transfers']
ft.send_file(self.account, contact, path)
elif httpupload.get_enabled():
con.check_file_before_transfer(
path, self.encryption, contact, self.session)
elif jingle.get_enabled():
ft = app.interface.instances['file_transfers']
ft.send_file(self.account, contact, path)
def get_seclabel(self):
label = None
if self.seclabel_combo is not None:
......@@ -1065,14 +1122,40 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
app.interface.instances['logs'] = \
history_window.HistoryWindow(jid, self.account)
def _on_send_file(self, gc_contact=None):
def _on_send_file(self, action, param):
# get file transfer preference
ft_pref = app.config.get_per('accounts', self.account,
'filetransfer_preference')
win = self.parent_win.window
httpupload = win.lookup_action(
'send-file-httpupload-%s' % self.control_id)
jingle = win.lookup_action('send-file-jingle-%s' % self.control_id)
if httpupload.get_enabled() and jingle.get_enabled():
if ft_pref == 'httpupload':
httpupload.activate()
else:
jingle.activate()
elif httpupload.get_enabled():
httpupload.activate()
elif jingle.get_enabled():
jingle.activate()
def _on_send_httpupload(self, action, param):
app.interface.send_httpupload(self)
def _on_send_jingle(self, action, param):
self._on_send_file_jingle()
def _on_send_file_jingle(self, gc_contact=None):
"""
gc_contact can be set when we are in a groupchat control
"""
def _on_ok(c):
app.interface.instances['file_transfers'].show_file_send_request(
self.account, c)
if self.TYPE_ID == message_control.TYPE_PM:
if self.type_id == message_control.TYPE_PM:
gc_contact = self.gc_contact
if gc_contact:
# gc or pm
......
......@@ -408,6 +408,8 @@ class Config:
'oauth2_redirect_url': [ opt_str, 'https%3A%2F%2Fgajim.org%2Fmsnauth%2Findex.cgi', _('redirect_url for OAuth 2.0 authentication.')],
'opened_chat_controls': [opt_str, '', _('Space separated list of JIDs for which we want to re-open a chat window on next startup.')],
'recent_groupchats': [ opt_str, '' ],
'httpupload_verify': [ opt_bool, True, _('HTTP Upload: Enable HTTPS Verification')],
'filetransfer_preference' : [ opt_str, 'httpupload', _('Preferred file transfer mechanism for file drag&drop on chat window. Can be \'httpupload\' (default) or \'jingle\'')],
}, {}),
'statusmsg': ({
'message': [ opt_str, '' ],
......
......@@ -422,7 +422,10 @@ class CommonConnection:
if not obj.is_loggable:
return
if obj.forward_from or not obj.session or not obj.session.is_loggable():
if obj.forward_from:
return
if obj.session and not obj.session.is_loggable():
return
if not app.config.should_log(self.name, jid):
......@@ -2662,7 +2665,7 @@ class Connection(CommonConnection, ConnectionHandlers):
if not obj.xhtml and app.config.get('rst_formatting_outgoing_messages'):
from gajim.common.rst_xhtml_generator import create_xhtml
obj.xhtml = create_xhtml(obj.message)
msg_iq = nbxmpp.Message(obj.jid, obj.message, typ='groupchat',
xhtml=obj.xhtml)
......
......@@ -54,6 +54,7 @@ from gajim.common.protocol.caps import ConnectionCaps
from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream
from gajim.common.protocol.bytestream import ConnectionIBBytestream
from gajim.common.message_archiving import ConnectionArchive313
from gajim.common.httpupload import ConnectionHTTPUpload
from gajim.common.connection_handlers_events import *
from gajim.common import ged
......@@ -1249,7 +1250,8 @@ class ConnectionHandlersBase:
class ConnectionHandlers(ConnectionArchive313,
ConnectionVcard, ConnectionSocks5Bytestream, ConnectionDisco,
ConnectionCommands, ConnectionPubSub, ConnectionPEP, ConnectionCaps,
ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream,
ConnectionHTTPUpload):
def __init__(self):
ConnectionArchive313.__init__(self)
ConnectionVcard.__init__(self)
......@@ -1259,6 +1261,7 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
ConnectionPubSub.__init__(self)
ConnectionPEP.__init__(self, account=self.name, dispatcher=self,
pubsub_connection=self)
ConnectionHTTPUpload.__init__(self, account=self.name)
# Handle presences BEFORE caps
app.nec.register_incoming_event(PresenceReceivedEvent)
......@@ -1343,6 +1346,7 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
ConnectionCaps.cleanup(self)
ConnectionArchive313.cleanup(self)
ConnectionPubSub.cleanup(self)
ConnectionHTTPUpload.cleanup(self)
app.ged.remove_event_handler('http-auth-received', ged.CORE,
self._nec_http_auth_received)
app.ged.remove_event_handler('version-request-received', ged.CORE,
......
......@@ -2958,3 +2958,17 @@ class BlockingEvent(nec.NetworkIncomingEvent):
app.log('blocking').info(
'Blocking Push - unblocked JIDs: %s', self.unblocked_jids)
return True
class HTTPUploadStartEvent(nec.NetworkIncomingEvent):
name = 'httpupload-start'
base_network_events = []
def generate(self):
return True
class HTTPUploadProgressEvent(nec.NetworkIncomingEvent):
name = 'httpupload-progress'
base_network_events = []
def generate(self):
return True
\ No newline at end of file
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<!-- Generated with glade 3.20.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkAdjustment" id="adjustment1">
......@@ -821,9 +821,6 @@ audio-mic-volume-low</property>
<property name="position">4</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkMenuButton" id="encryption_menu">
<property name="visible">True</property>
......@@ -846,9 +843,39 @@ audio-mic-volume-low</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkButton" id="sendfile_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="relief">none</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">mail-attachment-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
<style>
<class name="chatcontrol-actionbar-button"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">6</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<!-- Generated with glade 3.20.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkMenu" id="formattings_menu">
......@@ -340,6 +340,31 @@
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkButton" id="sendfile_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">mail-attachment-symbolic</property>
</object>
</child>
<style>
<class name="chatcontrol-actionbar-button"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">6</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.1 -->
<interface>
<requires lib="gtk+" version="3.14"/>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">8</property>
<property name="bottom_padding">4</property>
<property name="left_padding">8</property>
<property name="right_padding">8</property>
<child>
<object class="GtkLabel" id="label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="variant" value="normal"/>
</attributes>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">4</property>
<property name="bottom_padding">4</property>
<property name="left_padding">8</property>
<property name="right_padding">8</property>
<child>
<object class="GtkProgressBar" id="progressbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="pulse_step">0.10000000149</property>
<property name="show_text">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>
......@@ -133,6 +133,46 @@ messages = {
_('%s\nLink-local messaging might not work properly.'),
ErrorDialog),
'request-upload-slot-error': Message(
_('Could not request upload slot'),
'%s',
ErrorDialog),
'request-upload-slot-error2': Message(
_('Could not request upload slot'),
_('Got unexpected response from server (see log)'),
ErrorDialog),
'open-file-error': Message(
_('Could not open file'),
_('Exception raised while opening file (see log)'),
ErrorDialog),
'open-file-error2': Message(
_('Could not open file'),
'%s',
ErrorDialog),
'unsecure-error': Message(
_('Unsecure'),
_('Server returned unsecure transport (http)'),
ErrorDialog),
'httpupload-response-error': Message(
_('Could not upload file'),
_('HTTP response code from server: %s'),
ErrorDialog),
'httpupload-error': Message(
_('Upload Error'),
'%s',
ErrorDialog),
'httpupload-encryption-not-available': Message(
_('Encryption Error'),
_('For the choosen encryption is no encryption method available'),
ErrorDialog),
}
......
......@@ -5417,3 +5417,65 @@ class SSLErrorDialog(ConfirmationDialogDoubleCheck):
def on_cert_clicked(self, button):
d = CertificatDialog(self, self.account, self.cert)
class ProgressWindow(Gtk.ApplicationWindow):
def __init__(self, file):
Gtk.ApplicationWindow.__init__(self)
self.set_name('HTTPUploadProgressWindow')
self.set_application(app.app)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_show_menubar(False)
self.set_title(_('File Transfer'))
self.set_default_size(250, -1)
self.event = file.event
self.file = file
self.xml = gtkgui_helpers.get_gtk_builder(
'httpupload_progress_dialog.ui')
self.label = self.xml.get_object('label')
self.progressbar = self.xml.get_object('progressbar')
self.add(self.xml.get_object('box'))
self.pulse = GLib.timeout_add(100, self._pulse_progressbar)
self.show_all()
self.connect('destroy', self._on_destroy)
app.ged.register_event_handler('httpupload-progress', ged.CORE,
self._on_httpupload_progress)
def _on_httpupload_progress(self, obj):
if self.file != obj.file:
return
if obj.status == 'request':
self.label.set_text(_('Requesting HTTP Upload Slot...'))
elif obj.status == 'close':
self.destroy()
elif obj.status == 'upload':
self.label.set_text(_('Uploading file via HTTP File Upload...'))
elif obj.status == 'update':
self.update_progress(obj.seen, obj.total)
elif obj.status == 'encrypt':
self.label.set_text(_('Encrypting file...'))
def _pulse_progressbar(self):
self.progressbar.pulse()
return True
def _on_destroy(self, *args):
self.event.set()
if self.pulse:
GLib.source_remove(self.pulse)
app.ged.remove_event_handler('httpupload-progress', ged.CORE,
self._on_httpupload_progress)
def update_progress(self, seen, total):
if self.event.isSet():
return
if self.pulse:
GLib.source_remove(self.pulse)
self.pulse = None
pct = (float(seen) / total) * 100.0
self.progressbar.set_fraction(float(seen) / total)
self.progressbar.set_text(str(int(pct)) + "%")
......@@ -58,6 +58,7 @@ from gajim.common import helpers
from gajim.common import dataforms
from gajim.common import ged
from gajim.common import i18n
from gajim.common import contacts
from gajim.chat_control import ChatControl
from gajim.chat_control_base import ChatControlBase
......@@ -444,6 +445,11 @@ class GroupchatControl(ChatControlBase):
self.form_widget = None
# Send file
self.sendfile_button = self.xml.get_object('sendfile_button')
self.sendfile_button.set_action_name('win.send-file-' + \
self.control_id)
# Encryption
self.lock_image = self.xml.get_object('lock_image')
self.authentication_button = self.xml.get_object(
......@@ -589,6 +595,24 @@ class GroupchatControl(ChatControlBase):
win.lookup_action('execute-command-' + self.control_id).set_enabled(
online)
# Send file (HTTP File Upload)
httpupload = win.lookup_action(
'send-file-httpupload-' + self.control_id)
httpupload.set_enabled(
online and app.connections[self.account].httpupload)
win.lookup_action('send-file-' + self.control_id).set_enabled(
httpupload.get_enabled())
tooltip_text = None
if online:
if httpupload.get_enabled():
tooltip_text = _('HTTP File Upload')
else:
tooltip_text = _('HTTP File Upload not supported '
'by your server')
self.sendfile_button.set_tooltip_text(tooltip_text)
# Actions
def _on_change_subject(self, action, param):
......@@ -1565,12 +1589,6 @@ class GroupchatControl(ChatControlBase):
if ctrl and msg:
ctrl.send_message(msg)
def on_send_file(self, widget, gc_contact):
"""
Send a file to a contact in the room
"""
self._on_send_file(gc_contact)
def draw_contact(self, nick, selected=False, focus=False):
iter_ = self.get_contact_iter(nick)
if not iter_:
......@@ -2294,23 +2312,31 @@ class GroupchatControl(ChatControlBase):
ok_handler=on_ok, transient_for=self.parent_win.window)
def _on_drag_data_received(self, widget, context, x, y, selection,
target_type, timestamp):
# Invite contact to groupchat
treeview = app.interface.roster.tree
model = treeview.get_model()
if not selection.get_data() or target_type == 80:
# target_type = 80 means a file is dropped
target_type, timestamp):
if not selection.get_data():
return
data = selection.get_data()
path = treeview.get_selection().get_selected_rows()[1][0]
iter_ = model.get_iter(path)
type_ = model[iter_][2]
if type_ != 'contact': # source is not a contact
return
contact_jid = data
app.connections[self.account].send_invite(self.room_jid, contact_jid)
self.print_conversation(_('%(jid)s has been invited in this room') % {
'jid': contact_jid}, graphics=False)
# get contact info
contact = contacts.Contact(jid=self.room_jid, account=self.account)
if target_type == self.TARGET_TYPE_URI_LIST:
# file drag and drop (handled in chat_control_base)
self.drag_data_file_transfer(contact, selection, self)
else:
# Invite contact to groupchat
treeview = app.interface.roster.tree
model = treeview.get_model()
data = selection.get_data()
path = treeview.get_selection().get_selected_rows()[1][0]
iter_ = model.get_iter(path)
type_ = model[iter_][2]