# -*- coding: utf-8 -*- ## from common import demandimport demandimport.enable() demandimport.ignore += ['builtins', '__builtin__'] import gtk import gobject import os import base64 import urllib2 import mimetypes # better use the magic packet, but that's not a standard lib import gtkgui_helpers from Queue import Queue try: from PIL import Image except: pass import StringIO import base64 from common import gajim from common import ged import chat_control from plugins import GajimPlugin from plugins.helpers import log_calls, log from dialogs import FileChooserDialog, ImageChooserDialog, ErrorDialog import nbxmpp NS_HTTPUPLOAD = 'eu:siacs:conversations:http:upload' # XEP-0363 (http://xmpp.org/extensions/xep-0363.html) jid_to_servers = {} iq_ids_to_callbacks = {} max_thumbnail_size = 2048 max_thumbnail_dimension = 160 class HttpuploadPlugin(GajimPlugin): @log_calls('HttpuploadPlugin') def init(self): self.config_dialog = None # HttpuploadPluginConfigDialog(self) self.controls = [] self.events_handlers = {} self.events_handlers['agent-info-received'] = (ged.PRECORE, self.handle_agent_info_received) self.events_handlers['raw-iq-received'] = (ged.PRECORE, self.handle_iq_received) self.gui_extension_points = { 'chat_control_base': (self.connect_with_chat_control, self.disconnect_from_chat_control), 'chat_control_base_update_toolbar': (self.update_button_state, None)} self.first_run = True def handle_iq_received(self, event): global iq_ids_to_callbacks id_ = event.stanza.getAttr("id") if str(id_) in iq_ids_to_callbacks: try: iq_ids_to_callbacks[str(id_)](event.stanza) except: raise finally: del iq_ids_to_callbacks[str(id_)] def handle_agent_info_received(self, event): global jid_to_servers if NS_HTTPUPLOAD in event.features and gajim.jid_is_transport(event.jid): own_jid = gajim.get_jid_without_resource(str(event.stanza.getTo())) jid_to_servers[own_jid] = event.jid # map own jid to upload component's jid log.info(own_jid + " can do http uploads via component " + event.jid) # update all buttons for base in self.controls: self.update_button_state(base.chat_control) @log_calls('HttpuploadPlugin') def connect_with_chat_control(self, control): self.chat_control = control base = Base(self, self.chat_control) self.controls.append(base) if self.first_run: # ALT + U 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 @log_calls('HttpuploadPlugin') def disconnect_from_chat_control(self, chat_control): for control in self.controls: control.disconnect_from_chat_control() self.controls = [] @log_calls('HttpuploadPlugin') def update_button_state(self, chat_control): for base in self.controls: if base.chat_control == chat_control: if gajim.connections[chat_control.account].connection == None and \ gajim.get_jid_from_account(chat_control.account) in jid_to_servers: del jid_to_servers[gajim.get_jid_from_account(chat_control.account)] is_supported = gajim.get_jid_from_account(chat_control.account) in jid_to_servers and \ gajim.connections[chat_control.account].connection != None if not is_supported: text = _('Your server does not support http uploads') image_text = text else: text = _('Send file via http upload') image_text = _('Send image via http upload') base.button.set_sensitive(is_supported) base.button.set_tooltip_text(text) base.image_button.set_sensitive(is_supported) base.image_button.set_tooltip_text(image_text) class Base(object): def __init__(self, plugin, chat_control): self.dlg = None self.dialog_type = 'file' self.keypress_id = chat_control.msg_textview.connect('mykeypress', self.on_key_press) self.plugin = plugin self.chat_control = chat_control actions_hbox = chat_control.xml.get_object('actions_hbox') 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_sensitive(False) img = gtk.Image() img.set_from_file(os.path.join(gajim.gajimpaths.data_root, u'plugins', u'httpupload', u'httpupload.png')) self.button.set_image(img) 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.image_button.set_property('relief', gtk.RELIEF_NONE) self.image_button.set_property('can-focus', False) self.image_button.set_sensitive(False) img = gtk.Image() img.set_from_file(os.path.join(gajim.gajimpaths.data_root, u'plugins', u'httpupload', u'image.png')) self.image_button.set_image(img) self.image_button.set_tooltip_text(_('Your server does not support http uploads')) send_button = chat_control.xml.get_object('send_button') send_button_pos = actions_hbox.child_get_property(send_button, 'position') 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 - 1, 'expand', False) file_id = self.button.connect('clicked', self.on_file_button_clicked) image_id = self.image_button.connect('clicked', self.on_image_button_clicked) chat_control.handlers[file_id] = self.button chat_control.handlers[image_id] = self.image_button chat_control.handlers[self.keypress_id] = chat_control.msg_textview self.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): actions_hbox = self.chat_control.xml.get_object('actions_hbox') actions_hbox.remove(self.button) actions_hbox.remove(self.image_button) if 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 on_file_dialog_ok(self, widget, path_to_file=None): global jid_to_servers if not path_to_file: path_to_file = self.dlg.get_filename() if not path_to_file: self.dlg.destroy() return path_to_file = gtkgui_helpers.decode_filechooser_file_paths( (path_to_file,))[0] self.dlg.destroy() if not os.path.exists(path_to_file): return filesize = os.path.getsize(path_to_file) # in bytes invalid_file = False msg = '' if os.path.isfile(path_to_file): stat = os.stat(path_to_file) if stat[6] == 0: invalid_file = True msg = _('File is empty') else: invalid_file = True msg = _('File does not exist') if invalid_file: ErrorDialog(_('Could not open file'), msg, transient_for=self.chat_control.parent_win.window) return mime_type = mimetypes.MimeTypes().guess_type(path_to_file)[0] if not mime_type: mime_type = 'application/octet-stream' # fallback mime type log.info("Detected MIME Type of file: " + str(mime_type)) progress_messages = Queue(8) progress_window = ProgressWindow(_('HTTP Upload'), _('Requesting HTTP Upload Slot...'), progress_messages) def upload_file(stanza): try: open(path_to_file, "rb").read(1) # check for open/read errors (maybe this is not needed anymore) data = StreamFileWithProgress(path_to_file, "rb", progress_window.update_progress) except: progress_window.close_dialog() ErrorDialog(_('Could not open file'), _('Exception raised while opening file (see error log for more information)'), transient_for=self.chat_control.parent_win.window) raise # fill error log with useful information slot = stanza.getTag("slot") if not slot: progress_window.close_dialog() log.error("got unexpected stanza: "+str(stanza)) error = stanza.getTag("error") if error and error.getTag("text"): ErrorDialog(_('Could not request upload slot'), _('Got unexpected response from server: ') + str(error.getTagData("text")), transient_for=self.chat_control.parent_win.window) else: ErrorDialog(_('Could not request upload slot'), _('Got unexpected response from server (protocol mismatch??)'), transient_for=self.chat_control.parent_win.window) return put = slot.getTag("put") get = slot.getTag("get") if not put or not get: progress_window.close_dialog() log.error("got unexpected stanza: " + str(stanza)) ErrorDialog(_('Could not request upload slot'), _('Got unexpected response from server (protocol mismatch??)'), transient_for=self.chat_control.parent_win.window) return def upload_complete(response_code): if response_code == 0: return # Upload was aborted if response_code >= 200 and response_code < 300: log.info("Upload completed successfully") xhtml = None is_image = mime_type.split('/', 1)[0] == 'image' if (not isinstance(self.chat_control, chat_control.ChatControl) or not self.chat_control.gpg_is_active) and \ self.dialog_type == 'image' and is_image: progress_messages.put(_('Calculating (possible) image thumbnail...')) try: quality_steps = (100, 80, 60, 50, 40, 30, 25, 23, 20, 18, 15, 13, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) for quality in quality_steps: thumb = Image.open(path_to_file) thumb.thumbnail((max_thumbnail_dimension, max_thumbnail_dimension), Image.ANTIALIAS) output = StringIO.StringIO() thumb.save(output, format='JPEG', quality=quality, optimize=True) thumb = output.getvalue() output.close() thumb = urllib2.quote(base64.standard_b64encode(thumb), '') log.debug("thumbnail jpeg quality %d produces an image of size %d..." % (quality, len(thumb))) if len(thumb) < max_thumbnail_size: break if len(thumb) > max_thumbnail_size: log.info("Couldn't compress image enough, not sending any thumbnail") else: log.info("Using thumbnail jpeg quality %d (image size: %d bytes)" % (quality, len(thumb))) xhtml = '