Fix httpupload for GTK3

Not perfect yet, some layout issues remain, but it works.
This commit is contained in:
Linus Heckemann
2016-08-18 20:00:57 +02:00
parent 4ce02573cf
commit 17dfa9de94
3 changed files with 54 additions and 85 deletions

View File

@@ -1,2 +1,2 @@
# simple redirect # simple redirect
from httpupload import HttpuploadPlugin from .httpupload import HttpuploadPlugin

View File

@@ -1,20 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
## ##
from common import demandimport from gi.repository import GObject, Gtk
demandimport.enable()
demandimport.ignore += ['builtins', '__builtin__', 'PIL','_imp']
import gtk
import gobject
import os import os
import time import time
import base64 import base64
import tempfile import tempfile
import urllib2 from urllib.request import Request, urlopen
from urllib.parse import quote as urlquote
import mimetypes # better use the magic packet, but that's not a standard lib import mimetypes # better use the magic packet, but that's not a standard lib
import gtkgui_helpers import gtkgui_helpers
from Queue import Queue from queue import Queue
try: try:
from PIL import Image from PIL import Image
pil_available = True pil_available = True
@@ -105,10 +101,7 @@ class HttpuploadPlugin(GajimPlugin):
base = Base(self, self.chat_control) base = Base(self, self.chat_control)
self.controls.append(base) self.controls.append(base)
if self.first_run: if self.first_run:
# ALT + U # TODO: Potentially add back keyboard shortcut
gtk.binding_entry_add_signal(control.msg_textview,
gtk.keysyms.u, gtk.gdk.MOD1_MASK, 'mykeypress',
int, gtk.keysyms.u, gtk.gdk.ModifierType, gtk.gdk.MOD1_MASK)
self.first_run = False self.first_run = False
self.update_button_state(self.chat_control) self.update_button_state(self.chat_control)
@@ -190,73 +183,46 @@ class Base(object):
def __init__(self, plugin, chat_control): def __init__(self, plugin, chat_control):
self.dlg = None self.dlg = None
self.dialog_type = 'file' self.dialog_type = 'file'
self.keypress_id = chat_control.msg_textview.connect('mykeypress',
self.on_key_press)
self.plugin = plugin self.plugin = plugin
self.encrypted_upload = False self.encrypted_upload = False
self.chat_control = chat_control self.chat_control = chat_control
actions_hbox = chat_control.xml.get_object('actions_hbox') actions_hbox = chat_control.xml.get_object('actions_hbox')
self.button = gtk.Button(label=None, stock=None, use_underline=True) self.button = Gtk.Button(label=None, stock=None, use_underline=True)
self.button.set_property('relief', gtk.RELIEF_NONE)
self.button.set_property('can-focus', False) self.button.set_property('can-focus', False)
self.button.set_sensitive(False) self.button.set_sensitive(False)
img = gtk.Image() img = Gtk.Image()
img.set_from_file(self.plugin.local_file_path('httpupload.png')) img.set_from_file(self.plugin.local_file_path('httpupload.png'))
self.button.set_image(img) self.button.set_image(img)
self.button.set_tooltip_text(_('Your server does not support http uploads')) self.button.set_tooltip_text(_('Your server does not support http uploads'))
self.image_button = gtk.Button(label=None, stock=None, use_underline=True) self.button.set_relief(Gtk.ReliefStyle.NONE)
self.image_button.set_property('relief', gtk.RELIEF_NONE) self.image_button = Gtk.Button(label=None, stock=None, use_underline=True)
self.image_button.set_property('can-focus', False) self.image_button.set_property('can-focus', False)
self.image_button.set_relief(Gtk.ReliefStyle.NONE)
self.image_button.set_sensitive(False) self.image_button.set_sensitive(False)
img = gtk.Image() img = Gtk.Image()
img.set_from_file(self.plugin.local_file_path('image.png')) img.set_from_file(self.plugin.local_file_path('image.png'))
self.image_button.set_image(img) self.image_button.set_image(img)
self.image_button.set_tooltip_text(_('Your server does not support http uploads')) self.image_button.set_tooltip_text(_('Your server does not support http uploads'))
send_button = chat_control.xml.get_object('send_button') send_button = chat_control.xml.get_object('send_button')
send_button_pos = actions_hbox.child_get_property(send_button, actions_hbox.add(self.button)
'position') actions_hbox.add(self.image_button)
actions_hbox.add_with_properties(self.button, 'position',
send_button_pos - 2, 'expand', False)
actions_hbox.add_with_properties(self.image_button, 'position', send_button_pos = actions_hbox.child_get_property(send_button, 'position')
send_button_pos - 1, 'expand', False) actions_hbox.child_set_property(self.image_button, 'position', send_button_pos - 1)
actions_hbox.child_set_property(self.button, 'position', send_button_pos - 1)
file_id = self.button.connect('clicked', self.on_file_button_clicked) file_id = self.button.connect('clicked', self.on_file_button_clicked)
image_id = self.image_button.connect('clicked', self.on_image_button_clicked) image_id = self.image_button.connect('clicked', self.on_image_button_clicked)
chat_control.handlers[file_id] = self.button chat_control.handlers[file_id] = self.button
chat_control.handlers[image_id] = self.image_button chat_control.handlers[image_id] = self.image_button
chat_control.handlers[self.keypress_id] = chat_control.msg_textview
self.button.show() self.button.show()
self.image_button.show() self.image_button.show()
def on_key_press(self, widget, event_keyval, event_keymod):
# construct event instance from binding
event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
event.keyval = event_keyval
event.state = event_keymod
event.time = 0 # assign current time
if event.keyval != gtk.keysyms.u:
return
if event.state != gtk.gdk.MOD1_MASK: # ALT+u
return
is_supported = gajim.get_jid_from_account(self.chat_control.account) in jid_to_servers and \
gajim.connections[self.chat_control.account].connection != None
if not is_supported:
from dialogs import WarningDialog
WarningDialog('Warning', _('Your server does not support http uploads'),
transient_for=self.chat_control.parent_win.window)
return
self.on_file_button_clicked(widget)
def disconnect_from_chat_control(self): def disconnect_from_chat_control(self):
actions_hbox = self.chat_control.xml.get_object('actions_hbox') actions_hbox = self.chat_control.xml.get_object('actions_hbox')
actions_hbox.remove(self.button) actions_hbox.remove(self.button)
actions_hbox.remove(self.image_button) actions_hbox.remove(self.image_button)
if self.keypress_id in self.chat_control.handlers and \
self.chat_control.handlers[self.keypress_id].handler_is_connected(self.keypress_id):
self.chat_control.handlers[self.keypress_id].disconnect(self.keypress_id)
del self.chat_control.handlers[self.keypress_id]
def encryption_activated(self): def encryption_activated(self):
if not encryption_available: if not encryption_available:
@@ -289,8 +255,6 @@ class Base(object):
if not path_to_file: if not path_to_file:
self.dlg.destroy() self.dlg.destroy()
return return
path_to_file = gtkgui_helpers.decode_filechooser_file_paths(
(path_to_file,))[0]
self.dlg.destroy() self.dlg.destroy()
if not os.path.exists(path_to_file): if not os.path.exists(path_to_file):
return return
@@ -321,8 +285,8 @@ class Base(object):
def upload_file(stanza): def upload_file(stanza):
slot = stanza.getTag("slot") slot = stanza.getTag("slot")
if not slot: if not slot:
progress_window.close_dialog()
log.error("got unexpected stanza: "+str(stanza)) log.error("got unexpected stanza: "+str(stanza))
progress_window.close_dialog()
error = stanza.getTag("error") error = stanza.getTag("error")
if error and error.getTag("text"): if error and error.getTag("text"):
ErrorDialog(_('Could not request upload slot'), ErrorDialog(_('Could not request upload slot'),
@@ -347,6 +311,7 @@ class Base(object):
"rb", "rb",
progress_window.update_progress) progress_window.update_progress)
except: except:
log.error("Could not open file")
progress_window.close_dialog() progress_window.close_dialog()
ErrorDialog(_('Could not open file'), ErrorDialog(_('Could not open file'),
_('Exception raised while opening file (see error log for more information)'), _('Exception raised while opening file (see error log for more information)'),
@@ -356,8 +321,8 @@ class Base(object):
put = slot.getTag("put") put = slot.getTag("put")
get = slot.getTag("get") get = slot.getTag("get")
if not put or not get: if not put or not get:
progress_window.close_dialog()
log.error("got unexpected stanza: " + str(stanza)) log.error("got unexpected stanza: " + str(stanza))
progress_window.close_dialog()
ErrorDialog(_('Could not request upload slot'), ErrorDialog(_('Could not request upload slot'),
_('Got unexpected response from server (protocol mismatch??)'), _('Got unexpected response from server (protocol mismatch??)'),
transient_for=self.chat_control.parent_win.window) transient_for=self.chat_control.parent_win.window)
@@ -377,7 +342,7 @@ class Base(object):
thumb = None thumb = None
quality_steps = (100, 80, 60, 50, 40, 35, 30, 25, 23, 20, 18, 15, 13, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) quality_steps = (100, 80, 60, 50, 40, 35, 30, 25, 23, 20, 18, 15, 13, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
with open(path_to_file, 'rb') as content_file: with open(path_to_file, 'rb') as content_file:
thumb = urllib2.quote(base64.standard_b64encode(content_file.read()), '') thumb = urlquote(base64.standard_b64encode(content_file.read()), '')
if thumb and len(thumb) < max_thumbnail_size: if thumb and len(thumb) < max_thumbnail_size:
quality = 100 quality = 100
log.info("Image small enough (%d bytes), not resampling" % len(thumb)) log.info("Image small enough (%d bytes), not resampling" % len(thumb))
@@ -391,7 +356,7 @@ class Base(object):
thumb.save(output, format='JPEG', quality=quality, optimize=True) thumb.save(output, format='JPEG', quality=quality, optimize=True)
thumb = output.getvalue() thumb = output.getvalue()
output.close() output.close()
thumb = urllib2.quote(base64.standard_b64encode(thumb), '') thumb = urlquote(base64.standard_b64encode(thumb), '')
log.debug("pil thumbnail jpeg quality %d produces an image of size %d..." % (quality, len(thumb))) log.debug("pil thumbnail jpeg quality %d produces an image of size %d..." % (quality, len(thumb)))
if len(thumb) < max_thumbnail_size: if len(thumb) < max_thumbnail_size:
break break
@@ -405,7 +370,7 @@ class Base(object):
try: try:
with open(path_to_file, 'rb') as content_file: with open(path_to_file, 'rb') as content_file:
thumb = content_file.read() thumb = content_file.read()
loader = gtk.gdk.PixbufLoader() loader = Gtk.gdk.PixbufLoader()
loader.write(thumb) loader.write(thumb)
loader.close() loader.close()
pixbuf = loader.get_pixbuf() pixbuf = loader.get_pixbuf()
@@ -417,7 +382,7 @@ class Base(object):
scaled_pb.save(temp_file, "jpeg", {"quality": str(quality)}) scaled_pb.save(temp_file, "jpeg", {"quality": str(quality)})
with open(temp_file, 'rb') as content_file: with open(temp_file, 'rb') as content_file:
thumb = content_file.read() thumb = content_file.read()
thumb = urllib2.quote(base64.standard_b64encode(thumb), '') thumb = urlquote(base64.standard_b64encode(thumb), '')
log.debug("gtk thumbnail jpeg quality %d produces an image of size %d..." % (quality, len(thumb))) log.debug("gtk thumbnail jpeg quality %d produces an image of size %d..." % (quality, len(thumb)))
if len(thumb) < max_thumbnail_size: if len(thumb) < max_thumbnail_size:
break break
@@ -455,11 +420,12 @@ class Base(object):
try: try:
headers = {'User-Agent': 'Gajim %s' % gajim.version, headers = {'User-Agent': 'Gajim %s' % gajim.version,
'Content-Type': mime_type} 'Content-Type': mime_type}
request = urllib2.Request(put.getData().encode("utf-8"), data=data, headers=headers) request = Request(put.getData(), data=data, headers=headers)
request.get_method = lambda: 'PUT' request.get_method = lambda: 'PUT'
log.debug("opening urllib2 upload request...") log.debug("opening urllib upload request...")
transfer = urllib2.urlopen(request, timeout=30) transfer = urlopen(request, timeout=30)
log.debug("urllib2 upload request done, response code: " + str(transfer.getcode())) data.close()
log.debug("urllib upload request done, response code: " + str(transfer.getcode()))
return transfer.getcode() return transfer.getcode()
except UploadAbortedException: except UploadAbortedException:
log.info("Upload aborted") log.info("Upload aborted")
@@ -521,9 +487,9 @@ class Base(object):
def on_file_button_clicked(self, widget): def on_file_button_clicked(self, widget):
self.dialog_type = 'file' self.dialog_type = 'file'
self.dlg = FileChooserDialog(on_response_ok=self.on_file_dialog_ok, on_response_cancel=None, self.dlg = FileChooserDialog(on_response_ok=self.on_file_dialog_ok, on_response_cancel=None,
title_text = _('Choose file to send'), action = gtk.FILE_CHOOSER_ACTION_OPEN, title_text = _('Choose file to send'), action = Gtk.FileChooserAction.OPEN,
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK), buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK),
default_response = gtk.RESPONSE_OK,) default_response = Gtk.ResponseType.OK,)
def on_image_button_clicked(self, widget): def on_image_button_clicked(self, widget):
self.dialog_type = 'image' self.dialog_type = 'image'
@@ -546,25 +512,25 @@ class Base(object):
image_height = int(size) image_height = int(size)
crop_pixbuf = pixbuf.scale_simple(image_width, image_height, crop_pixbuf = pixbuf.scale_simple(image_width, image_height,
gtk.gdk.INTERP_BILINEAR) Gtk.gdk.INTERP_BILINEAR)
return crop_pixbuf return crop_pixbuf
class StreamFileWithProgress(file): class StreamFileWithProgress:
def __init__(self, path, mode, callback=None, def __init__(self, path, mode, callback=None,
encrypted_upload=False, key=None, iv=None, *args): encrypted_upload=False, key=None, iv=None, *args):
file.__init__(self, path, mode) self.backing = open(path, mode)
self.encrypted_upload = encrypted_upload self.encrypted_upload = encrypted_upload
self.seek(0, os.SEEK_END) self.backing.seek(0, os.SEEK_END)
if self.encrypted_upload: if self.encrypted_upload:
self.encryptor = Cipher( self.encryptor = Cipher(
algorithms.AES(key), algorithms.AES(key),
GCM(iv), GCM(iv),
backend=default_backend()).encryptor() backend=default_backend()).encryptor()
self._total = self.tell() + TAGSIZE self._total = self.backing.tell() + TAGSIZE
else: else:
self._total = self.tell() self._total = self.backing.tell()
self.seek(0) self.backing.seek(0)
self._callback = callback self._callback = callback
self._args = args self._args = args
self._seen = 0 self._seen = 0
@@ -574,7 +540,7 @@ class StreamFileWithProgress(file):
def read(self, size): def read(self, size):
if self.encrypted_upload: if self.encrypted_upload:
data = file.read(self, size) data = self.backing.read(size)
if len(data) > 0: if len(data) > 0:
data = self.encryptor.update(data) data = self.encryptor.update(data)
self._seen += len(data) self._seen += len(data)
@@ -586,12 +552,15 @@ class StreamFileWithProgress(file):
self._callback(self._seen, self._total, *self._args) self._callback(self._seen, self._total, *self._args)
return data return data
else: else:
data = file.read(self, size) data = self.backing.read(size)
self._seen += len(data) self._seen += len(data)
if self._callback: if self._callback:
self._callback(self._seen, self._total, *self._args) self._callback(self._seen, self._total, *self._args)
return data return data
def close(self):
return self.backing.close()
class ProgressWindow: class ProgressWindow:
def __init__(self, title_text, during_text, messages_queue, plugin): def __init__(self, title_text, during_text, messages_queue, plugin):
@@ -605,14 +574,14 @@ class ProgressWindow:
self.progressbar = self.xml.get_object('progressbar') self.progressbar = self.xml.get_object('progressbar')
self.progressbar.set_text("") self.progressbar.set_text("")
self.dialog.set_title(title_text) self.dialog.set_title(title_text)
self.dialog.set_geometry_hints(min_width=400, min_height=96) #self.dialog.set_geometry_hints(min_width=400, min_height=96)
self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) #self.dialog.set_position(Gtk.WIN_POS_CENTER_ON_PARENT)
self.dialog.show_all() self.dialog.show_all()
self.xml.connect_signals(self) self.xml.connect_signals(self)
self.stopped = False self.stopped = False
self.pulse_progressbar_timeout_id = gobject.timeout_add(100, self.pulse_progressbar) self.pulse_progressbar_timeout_id = GObject.timeout_add(100, self.pulse_progressbar)
self.process_messages_queue_timeout_id = gobject.timeout_add(100, self.process_messages_queue) self.process_messages_queue_timeout_id = GObject.timeout_add(100, self.process_messages_queue)
def pulse_progressbar(self): def pulse_progressbar(self):
@@ -631,21 +600,21 @@ class ProgressWindow:
def on_progress_dialog_delete_event(self, widget, event): def on_progress_dialog_delete_event(self, widget, event):
self.stopped = True self.stopped = True
if self.pulse_progressbar_timeout_id: if self.pulse_progressbar_timeout_id:
gobject.source_remove(self.pulse_progressbar_timeout_id) GObject.source_remove(self.pulse_progressbar_timeout_id)
gobject.source_remove(self.process_messages_queue_timeout_id) GObject.source_remove(self.process_messages_queue_timeout_id)
def on_cancel(self, widget): def on_cancel(self, widget):
self.stopped = True self.stopped = True
if self.pulse_progressbar_timeout_id: if self.pulse_progressbar_timeout_id:
gobject.source_remove(self.pulse_progressbar_timeout_id) GObject.source_remove(self.pulse_progressbar_timeout_id)
gobject.source_remove(self.process_messages_queue_timeout_id) GObject.source_remove(self.process_messages_queue_timeout_id)
self.dialog.destroy() self.dialog.destroy()
def update_progress(self, seen, total): def update_progress(self, seen, total):
if self.stopped == True: if self.stopped == True:
raise UploadAbortedException raise UploadAbortedException
if self.pulse_progressbar_timeout_id: if self.pulse_progressbar_timeout_id:
gobject.source_remove(self.pulse_progressbar_timeout_id) GObject.source_remove(self.pulse_progressbar_timeout_id)
self.pulse_progressbar_timeout_id = None self.pulse_progressbar_timeout_id = None
pct = (float(seen) / total) * 100.0 pct = (float(seen) / total) * 100.0
self.progressbar.set_fraction(float(seen) / total) self.progressbar.set_fraction(float(seen) / total)

View File

@@ -13,4 +13,4 @@ authors: Thilo Molitor <thilo@eightysoft.de>
Philipp Hörist <philipp@hoerist.com> Philipp Hörist <philipp@hoerist.com>
homepage: https://trac-plugins.gajim.org/wiki/HttpUploadPlugin homepage: https://trac-plugins.gajim.org/wiki/HttpUploadPlugin
min_gajim_version: 0.16.5 min_gajim_version: 0.16.5
max_gajim_version: 0.16.9 max_gajim_version: 0.16.10.1