diff options
-rw-r--r-- | gajim/chat_control_base.py | 8 | ||||
-rw-r--r-- | gajim/common/const.py | 11 | ||||
-rw-r--r-- | gajim/common/exceptions.py | 4 | ||||
-rw-r--r-- | gajim/common/filetransfer.py | 26 | ||||
-rw-r--r-- | gajim/common/helpers.py | 7 | ||||
-rw-r--r-- | gajim/common/modules/httpupload.py | 118 | ||||
-rw-r--r-- | gajim/dialog_messages.py | 31 | ||||
-rw-r--r-- | gajim/gtk/filetransfer_progress.py | 13 | ||||
-rw-r--r-- | gajim/gui_interface.py | 56 |
9 files changed, 148 insertions, 126 deletions
diff --git a/gajim/chat_control_base.py b/gajim/chat_control_base.py index 51e9c92e8..cedd01c6b 100644 --- a/gajim/chat_control_base.py +++ b/gajim/chat_control_base.py @@ -814,14 +814,8 @@ class ChatControlBase(ChatCommandProcessor, CommandTools, EventHelper): if method is None: return - con = app.connections[self.account] - if method == 'httpupload': - con.get_module('HTTPUpload').check_file_before_transfer( - path, - self.encryption, - self.contact, - groupchat=self._type.is_groupchat) + app.interface.send_httpupload(self, path) else: ft = app.interface.instances['file_transfers'] diff --git a/gajim/common/const.py b/gajim/common/const.py index 665fba11e..0f5087267 100644 --- a/gajim/common/const.py +++ b/gajim/common/const.py @@ -908,6 +908,7 @@ class FTState(Enum): IN_PROGRESS = 'progress' FINISHED = 'finished' ERROR = 'error' + CANCELLED = 'cancelled' @property def is_preparing(self): @@ -937,6 +938,16 @@ class FTState(Enum): def is_error(self): return self == FTState.ERROR + @property + def is_cancelled(self): + return self == FTState.CANCELLED + + @property + def is_active(self): + return not (self.is_error or + self.is_cancelled or + self.is_finished) + SASL_ERRORS = { 'aborted': _('Authentication aborted'), diff --git a/gajim/common/exceptions.py b/gajim/common/exceptions.py index ab5450c56..5e6b06bf6 100644 --- a/gajim/common/exceptions.py +++ b/gajim/common/exceptions.py @@ -151,3 +151,7 @@ class StanzaMalformed(Exception): class SendMessageError(Exception): pass + + +class FileError(Exception): + pass diff --git a/gajim/common/filetransfer.py b/gajim/common/filetransfer.py index b0085984c..f600fc9c6 100644 --- a/gajim/common/filetransfer.py +++ b/gajim/common/filetransfer.py @@ -21,23 +21,27 @@ class FileTransfer(Observable): _state_descriptions = {} # type: Dict[FTState, str] - def __init__(self, account, cancel_func=None): + def __init__(self, account): Observable.__init__(self) self._account = account - self._cancel_func = cancel_func self._seen = 0 self.size = 0 self._state = None self._error_text = '' + self._error_domain = None @property def account(self): return self._account @property + def state(self): + return self._state + + @property def seen(self): return self._seen @@ -51,9 +55,13 @@ class FileTransfer(Observable): def filename(self): raise NotImplementedError - def cancel(self): - if self._cancel_func is not None: - self._cancel_func(self) + @property + def error_text(self): + return self._error_text + + @property + def error_domain(self): + return self._error_domain def get_state_description(self): return self._state_descriptions.get(self._state, '') @@ -74,12 +82,18 @@ class FileTransfer(Observable): self._state = FTState.STARTED self.notify('state-changed', FTState.STARTED) - def set_error(self, text=''): + def set_error(self, domain, text=''): self._error_text = text + self._error_domain = domain self._state = FTState.ERROR self.notify('state-changed', FTState.ERROR) self.disconnect_signals() + def set_cancelled(self): + self._state = FTState.CANCELLED + self.notify('state-changed', FTState.CANCELLED) + self.disconnect_signals() + def set_in_progress(self): self._state = FTState.IN_PROGRESS self.notify('state-changed', FTState.IN_PROGRESS) diff --git a/gajim/common/helpers.py b/gajim/common/helpers.py index bca56eb01..28d3fd8a0 100644 --- a/gajim/common/helpers.py +++ b/gajim/common/helpers.py @@ -45,6 +45,7 @@ import functools from collections import defaultdict import random import weakref +import inspect import string from string import Template import urllib @@ -1256,7 +1257,11 @@ class Observable: self._callbacks[signal_name].remove(handler) def connect(self, signal_name, func): - weak_func = weakref.WeakMethod(func) + if inspect.ismethod(func): + weak_func = weakref.WeakMethod(func) + elif inspect.isfunction(func): + weak_func = weakref.ref(func) + self._callbacks[signal_name].append(weak_func) def notify(self, signal_name, *args, **kwargs): diff --git a/gajim/common/modules/httpupload.py b/gajim/common/modules/httpupload.py index 0b2783061..0d29d3012 100644 --- a/gajim/common/modules/httpupload.py +++ b/gajim/common/modules/httpupload.py @@ -35,8 +35,7 @@ from gajim.common.helpers import get_user_proxy from gajim.common.const import FTState from gajim.common.filetransfer import FileTransfer from gajim.common.modules.base import BaseModule -from gajim.common.structs import OutgoingMessage -from gajim.common.connection_handlers_events import InformationEvent +from gajim.common.exceptions import FileError class HTTPUpload(BaseModule): @@ -87,10 +86,9 @@ class HTTPUpload(BaseModule): for ctrl in app.interface.msg_win_mgr.get_controls(acct=self._account): ctrl.update_actions() - def check_file_before_transfer(self, path, encryption, contact, - groupchat=False): + def make_transfer(self, path, encryption, contact, groupchat=False): if not path or not os.path.exists(path): - return + return None invalid_file = False stat = os.stat(path) @@ -112,47 +110,41 @@ class HTTPUpload(BaseModule): 'maximum allowed file size is: %s') % size if invalid_file: - self._raise_information_event('open-file-error2', msg) - return + raise FileError('file-error', msg) mime = mimetypes.MimeTypes().guess_type(path)[0] if not mime: mime = 'application/octet-stream' # fallback mime type self._log.info("Detected MIME type of file: %s", mime) - try: - transfer = HTTPFileTransfer(self._account, - self._cancel_upload, - path, - contact, - mime, - encryption, - groupchat) - app.interface.show_httpupload_progress(transfer) - except Exception as error: - self._log.exception('Error while loading file') - self._raise_information_event('open-file-error2', str(error)) - return - - if encryption is not None: - app.interface.encrypt_file(transfer, - self._account, - self._request_slot) - else: - self._request_slot(transfer) + return HTTPFileTransfer(self._account, + path, + contact, + mime, + encryption, + groupchat) - def _cancel_upload(self, transfer): + def cancel_transfer(self, transfer): + transfer.set_cancelled() message = self._queued_messages.get(id(transfer)) if message is None: return + self._session.cancel_message(message, Soup.Status.CANCELLED) - @staticmethod - def _raise_information_event(dialog_name, args=None): - app.nec.push_incoming_event(InformationEvent( - None, dialog_name=dialog_name, args=args)) + def start_transfer(self, transfer): + if transfer.encryption is not None and not transfer.is_encrypted: + transfer.set_encrypting() + plugin = app.plugin_manager.encryption_plugins[transfer.encryption] + if hasattr(plugin, 'encrypt_file'): + plugin.encrypt_file(transfer, + self._account, + self.start_transfer) + else: + transfer.set_error('encryption-not-available') + + return - def _request_slot(self, transfer): transfer.set_preparing() self._log.info('Sending request for slot') self._nbxmpp('HTTPUpload').request_slot( @@ -172,8 +164,6 @@ class HTTPUpload(BaseModule): HTTPUploadStanzaError, MalformedStanzaError) as error: - transfer.set_error() - if error.app_condition == 'file-too-large': size_text = GLib.format_size_full( error.get_max_file_size(), @@ -181,20 +171,18 @@ class HTTPUpload(BaseModule): error_text = _('File is too large, ' 'maximum allowed file size is: %s' % size_text) + transfer.set_error('file-too-large', error_text) + else: - error_text = str(error) - self._log.warning(error) + transfer.set_error('misc', str(error)) - self._raise_information_event('request-upload-slot-error', - error_text) return transfer.process_result(result) if (urlparse(transfer.put_uri).scheme != 'https' or urlparse(transfer.get_uri).scheme != 'https'): - transfer.set_error() - self._raise_information_event('unsecure-error') + transfer.set_error('unsecure') return self._log.info('Uploading file to %s', transfer.put_uri) @@ -206,7 +194,7 @@ class HTTPUpload(BaseModule): transfer.set_started() message = Soup.Message.new('PUT', transfer.put_uri) - message.connect('starting', self._check_certificate) + message.connect('starting', self._check_certificate, transfer) # Set CAN_REBUILD so chunks get discarded after they have been # written to the network @@ -227,10 +215,11 @@ class HTTPUpload(BaseModule): self._set_proxy_if_available() self._session.queue_message(message, self._on_finish, transfer) - def _check_certificate(self, message): + def _check_certificate(self, message, transfer): https_used, tls_certificate, tls_errors = message.get_https_status() if not https_used: self._log.warning('HTTPS was not used for upload') + transfer.set_error('unsecure') self._session.cancel_message(message, Soup.Status.CANCELLED) return @@ -241,12 +230,12 @@ class HTTPUpload(BaseModule): for error in tls_errors: phrase = get_tls_error_phrase(error) self._log.warning('TLS verification failed: %s', phrase) + + transfer.set_error('tls-verification-failed', phrase) self._session.cancel_message(message, Soup.Status.CANCELLED) - self._raise_information_event('httpupload-error', phrase) def _on_finish(self, _session, message, transfer): self._queued_messages.pop(id(transfer), None) - transfer.set_finished() if message.props.status_code == Soup.Status.CANCELLED: self._log.info('Upload cancelled') @@ -254,25 +243,14 @@ class HTTPUpload(BaseModule): if message.props.status_code in (Soup.Status.OK, Soup.Status.CREATED): self._log.info('Upload completed successfully') - uri = transfer.get_transformed_uri() + transfer.set_finished() - type_ = 'chat' - if transfer.is_groupchat: - type_ = 'groupchat' - - message = OutgoingMessage(account=self._account, - contact=transfer.contact, - message=uri, - type_=type_, - oob_url=uri) - - self._con.send_message(message) else: phrase = Soup.Status.get_phrase(message.props.status_code) self._log.error('Got unexpected http upload response code: %s', phrase) - self._raise_information_event('httpupload-response-error', phrase) + transfer.set_error('http-response', phrase) def _on_wrote_chunk(self, message, transfer): transfer.update_progress() @@ -303,15 +281,21 @@ class HTTPFileTransfer(FileTransfer): FTState.STARTED: _('Uploading via HTTP File Upload…'), } + _errors = { + 'unsecure': _('The server returned an insecure transport (HTTP).'), + 'encryption-not-available': _('There is no encryption method available ' + 'for the chosen encryption.') + } + def __init__(self, account, - cancel_func, path, contact, mime, encryption, groupchat): - FileTransfer.__init__(self, account, cancel_func=cancel_func) + + FileTransfer.__init__(self, account) self._path = path self._encryption = encryption @@ -328,6 +312,8 @@ class HTTPFileTransfer(FileTransfer): self._data = None self._headers = {} + self._is_encrypted = False + @property def mime(self): return self._mime @@ -352,6 +338,10 @@ class HTTPFileTransfer(FileTransfer): def path(self): return self._path + @property + def is_encrypted(self): + return self._is_encrypted + def get_transformed_uri(self): if self._uri_transform_func is not None: return self._uri_transform_func(self.get_uri) @@ -364,9 +354,12 @@ class HTTPFileTransfer(FileTransfer): def filename(self): return os.path.basename(self._path) - def set_error(self, text=''): + def set_error(self, domain, text=''): + if not text: + text = self._errors[domain] + self._close() - super().set_error(text) + super().set_error(domain, text) def set_finished(self): self._close() @@ -374,6 +367,7 @@ class HTTPFileTransfer(FileTransfer): def set_encrypted_data(self, data): self._data = data + self._is_encrypted = True def _close(self): if self._stream is not None: diff --git a/gajim/dialog_messages.py b/gajim/dialog_messages.py index bdcad6744..532b6854f 100644 --- a/gajim/dialog_messages.py +++ b/gajim/dialog_messages.py @@ -97,42 +97,11 @@ messages = { _('%s\nLink-local messaging might not work properly.'), ErrorDialog), - 'request-upload-slot-error': Message( - _('Could not request upload slot for HTTP File Upload'), - '%s', - ErrorDialog), - - 'open-file-error': Message( - _('Could not Open File'), - _('Exception raised while trying to open file (see log).'), - ErrorDialog), - 'open-file-error2': Message( _('Could not Open File'), '%s', ErrorDialog), - 'unsecure-error': Message( - _('Not Secure'), - _('The server returned an insecure 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'), - _('There is no encryption method available ' - 'for the chosen encryption.'), - ErrorDialog), - } diff --git a/gajim/gtk/filetransfer_progress.py b/gajim/gtk/filetransfer_progress.py index 769782354..d6cf4c837 100644 --- a/gajim/gtk/filetransfer_progress.py +++ b/gajim/gtk/filetransfer_progress.py @@ -22,6 +22,7 @@ from gajim.common.i18n import _ from .util import get_builder from .util import EventHelper +from .dialogs import ErrorDialog class FileTransferProgress(Gtk.ApplicationWindow, EventHelper): @@ -57,7 +58,11 @@ class FileTransferProgress(Gtk.ApplicationWindow, EventHelper): self._ui.connect_signals(self) def _on_transfer_state_change(self, transfer, _signal_name, state): - if state.is_finished or state.is_error: + if state.is_error: + ErrorDialog(_('Upload Failed'), transfer.error_text) + self.destroy() + + if state.is_finished or state.is_cancelled: self.destroy() return @@ -73,8 +78,10 @@ class FileTransferProgress(Gtk.ApplicationWindow, EventHelper): self.destroy() def _on_destroy(self, *args): - self._transfer.cancel() - self._transfer.disconnect(self) + if self._transfer.state.is_active: + client = app.get_client(self._transfer.account) + client.get_module('HTTPUpload').cancel_transfer(self._transfer) + self._transfer = None self._destroyed = True if self._pulse is not None: diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index 818cdca61..09ad4f612 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -77,6 +77,7 @@ from gajim.common import logging_helpers from gajim.common.helpers import ask_for_status_message from gajim.common.helpers import get_group_chat_nick from gajim.common.structs import MUCData +from gajim.common.structs import OutgoingMessage from gajim.common.nec import NetworkEvent from gajim.common.i18n import _ from gajim.common.client import Client @@ -84,9 +85,11 @@ from gajim.common.const import Display from gajim.common.const import JingleState from gajim.common.file_props import FilesProp +from gajim.common.connection_handlers_events import InformationEvent from gajim import roster_window from gajim.common import ged +from gajim.common.exceptions import FileError from gajim.gui.avatar import AvatarStorage from gajim.gui.notification import Notification @@ -849,34 +852,55 @@ class Interface: if ask_for_status_message(obj.conn.status, signin=True): open_window('StatusChange', status=obj.conn.status) - @staticmethod - def show_httpupload_progress(transfer): - FileTransferProgress(transfer) + def send_httpupload(self, chat_control, path=None): + if path is not None: + self._send_httpupload(chat_control, path) + return - def send_httpupload(self, chat_control): accept_cb = partial(self.on_file_dialog_ok, chat_control) FileChooserDialog(accept_cb, select_multiple=True, transient_for=chat_control.parent_win.window) - @staticmethod - def on_file_dialog_ok(chat_control, paths): - con = app.connections[chat_control.account] + def on_file_dialog_ok(self, chat_control, paths): for path in paths: - con.get_module('HTTPUpload').check_file_before_transfer( + self._send_httpupload(chat_control, path) + + def _send_httpupload(self, chat_control, path): + con = app.connections[chat_control.account] + try: + transfer = con.get_module('HTTPUpload').make_transfer( path, chat_control.encryption, chat_control.contact, chat_control.is_groupchat) + except FileError as error: + app.nec.push_incoming_event(InformationEvent( + None, dialog_name='open-file-error2', args=error)) + return - def encrypt_file(self, transfer, account, callback): - transfer.set_encrypting() - plugin = app.plugin_manager.encryption_plugins[transfer.encryption] - if hasattr(plugin, 'encrypt_file'): - plugin.encrypt_file(transfer, account, callback) - else: - transfer.set_error() - self.raise_dialog('httpupload-encryption-not-available') + transfer.connect('state-changed', + self._on_http_upload_state_changed) + FileTransferProgress(transfer) + con.get_module('HTTPUpload').start_transfer(transfer) + + @staticmethod + def _on_http_upload_state_changed(transfer, _signal_name, state): + if state.is_finished: + uri = transfer.get_transformed_uri() + + type_ = 'chat' + if transfer.is_groupchat: + type_ = 'groupchat' + + message = OutgoingMessage(account=transfer.account, + contact=transfer.contact, + message=uri, + type_=type_, + oob_url=uri) + + client = app.get_client(transfer.account) + client.send_message(message) @staticmethod def handle_event_metacontacts(obj): |