Philipp Hörist pushed to branch master at gajim / gajim
Commits: a290db02 by lovetox at 2020-12-20T16:35:11+01:00 HTTPUpload: Separate transfer from sending message
- - - - -
9 changed files:
- gajim/chat_control_base.py - gajim/common/const.py - gajim/common/exceptions.py - gajim/common/filetransfer.py - gajim/common/helpers.py - gajim/common/modules/httpupload.py - gajim/dialog_messages.py - gajim/gtk/filetransfer_progress.py - gajim/gui_interface.py
Changes:
===================================== gajim/chat_control_base.py ===================================== @@ -814,14 +814,8 @@ def _start_filetransfer(self, path): 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']
===================================== 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 @@ def is_finished(self): 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'),
===================================== gajim/common/exceptions.py ===================================== @@ -151,3 +151,7 @@ def __str__(self):
class SendMessageError(Exception): pass + + +class FileError(Exception): + pass
===================================== gajim/common/filetransfer.py ===================================== @@ -21,22 +21,26 @@ 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 @@ def is_complete(self): 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 @@ def set_started(self): 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)
===================================== gajim/common/helpers.py ===================================== @@ -45,6 +45,7 @@ from collections import defaultdict import random import weakref +import inspect import string from string import Template import urllib @@ -1256,7 +1257,11 @@ def disconnect(self, object_): 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):
===================================== gajim/common/modules/httpupload.py ===================================== @@ -35,8 +35,7 @@ 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 @@ def pass_disco(self, info): 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 @@ def check_file_before_transfer(self, path, encryption, contact, '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 @@ def _received_slot(self, task): 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 @@ def _received_slot(self, task):
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 @@ def _upload_file(self, transfer): 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 @@ def _upload_file(self, transfer): 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 @@ def _check_certificate(self, message): 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 @@ def _on_finish(self, _session, message, transfer):
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 @@ def __init__(self, self._data = None self._headers = {}
+ self._is_encrypted = False + @property def mime(self): return self._mime @@ -352,6 +338,10 @@ def headers(self): 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 @@ def set_uri_transform_func(self, func): 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 @@ def set_finished(self):
def set_encrypted_data(self, data): self._data = data + self._is_encrypted = True
def _close(self): if self._stream is not None:
===================================== gajim/dialog_messages.py ===================================== @@ -97,42 +97,11 @@ _('%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), - }
===================================== gajim/gtk/filetransfer_progress.py ===================================== @@ -22,6 +22,7 @@
from .util import get_builder from .util import EventHelper +from .dialogs import ErrorDialog
class FileTransferProgress(Gtk.ApplicationWindow, EventHelper): @@ -57,7 +58,11 @@ def __init__(self, transfer): 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 @@ def _on_cancel_button_clicked(self, _widget): 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:
===================================== gajim/gui_interface.py ===================================== @@ -77,6 +77,7 @@ 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 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 @@ def handle_event_signed_in(self, obj): 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):
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/commit/a290db02c14bc62b2148d49e6070600ea...