Daniel Brötzmann pushed to branch mainwindow at gajim / gajim
Commits: b263c91b by wurstsalat at 2021-10-09T21:07:26+02:00 ChatControl: Remove unused variable
- - - - - f5da2cfa by wurstsalat at 2021-10-13T21:36:53+02:00 Jingle File Transfer: Rework GUI handling
- - - - - 5bb571e7 by wurstsalat at 2021-10-13T21:37:35+02:00 StatusIcon: Fix update_icon
- - - - - 4e39fe22 by wurstsalat at 2021-10-13T21:38:01+02:00 Events: Remove file transfer events
- - - - - 1d06b434 by wurstsalat at 2021-10-13T21:38:15+02:00 ChatControl: Remove info bar
- - - - - bede5d20 by wurstsalat at 2021-10-13T21:42:08+02:00 Settings: Remove autopopup*
- - - - -
16 changed files:
- gajim/common/connection_handlers_events.py - gajim/common/events.py - gajim/common/helpers.py - gajim/common/setting_values.py - + gajim/data/gui/file_transfer_jingle.ui - gajim/gtk/controls/base.py - gajim/gtk/controls/chat.py - gajim/gtk/conversation/rows/file_transfer.py - + gajim/gtk/conversation/rows/file_transfer_jingle.py - gajim/gtk/conversation/view.py - gajim/gtk/filetransfer.py - gajim/gtk/main.py - gajim/gtk/preferences.py - gajim/gtk/status_icon.py - gajim/gtk/util.py - gajim/gui_interface.py
Changes:
===================================== gajim/common/connection_handlers_events.py ===================================== @@ -75,6 +75,7 @@ def generate(self): self.id_ = self.stanza.getID() self.fjid = self.conn.get_module('Bytestream')._ft_get_from( self.stanza) + self.account = self.conn.name self.jid = app.get_jid_without_resource(self.fjid) if not self.jingle_content: return
===================================== gajim/common/events.py ===================================== @@ -27,11 +27,8 @@ class Event: def __init__(self, time_=None): """ type_ in chat-message, group-chat-message, private-chat-message, - file-request, file-request-error, file-error, file-completed, - file-send-error, file-stopped,
parameters is (per type_): - file-*: file_props *-chat-message: [message, subject, control, msg_log_id] """ if time_: @@ -74,47 +71,6 @@ class PrivateChatMsgEvent(ChatMsgEvent): type_ = 'private-chat-message'
-class FileRequestEvent(Event): - - type_ = 'file-request' - - def __init__(self, - file_props, - time_=None): - Event.__init__(self, time_) - self.file_props = file_props - - -class FileSendErrorEvent(FileRequestEvent): - - type_ = 'file-send-error' - - -class FileErrorEvent(FileRequestEvent): - - type_ = 'file-error' - - -class FileRequestErrorEvent(FileRequestEvent): - - type_ = 'file-request-error' - - -class FileCompletedEvent(FileRequestEvent): - - type_ = 'file-completed' - - -class FileStoppedEvent(FileRequestEvent): - - type_ = 'file-stopped' - - -class FileHashErrorEvent(FileRequestEvent): - - type_ = 'file-hash-error' - - class Events: def __init__(self): self._events = {} # list of events {acct: {jid1: [E1, E2]}, }
===================================== gajim/common/helpers.py ===================================== @@ -688,18 +688,6 @@ def allow_showing_notification(account): return False
-def allow_popup_window(account): - """ - Is it allowed to popup windows? - """ - autopopup = app.settings.get('autopopup') - autopopupaway = app.settings.get('autopopupaway') - client = app.get_client(account) - if autopopup and (autopopupaway or client.status == 'online'): - return True - return False - - def allow_sound_notification(account, sound_event): if not app.settings.get('sounds_on'): return False
===================================== gajim/common/setting_values.py ===================================== @@ -15,10 +15,8 @@ class _ACCOUNT_DEFAULT: # pylint: disable=line-too-long
APP_SETTINGS = { - 'autopopup': False, 'show_notifications': True, 'show_notifications_away': False, - 'autopopupaway': False, 'sounddnd': False, 'showoffline': True, 'show_only_chat_and_online': False,
===================================== gajim/data/gui/file_transfer_jingle.ui ===================================== @@ -0,0 +1,434 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.38.2 --> +<interface> + <requires lib="gtk+" version="3.24"/> + <object class="GtkBox" id="transfer_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">document-send-symbolic</property> + <property name="icon_size">6</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="transfer_action"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="label"><transfer action></property> + <property name="wrap">True</property> + <property name="max-width-chars">50</property> + <style> + <class name="bold"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">start</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="file_name"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="label"><file name></property> + <property name="ellipsize">middle</property> + <property name="max-width-chars">20</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="file_description"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="label"><file description></property> + <property name="ellipsize">end</property> + <property name="max-width-chars">20</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="file_size"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="label"><file size></property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkStack" id="action_stack"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">start</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">Download</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="valign">start</property> + <signal name="clicked" handler="_on_accept_file_request" swapped="no"/> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="tooltip-text" translatable="yes">Reject file transfer</property> + <property name="valign">start</property> + <signal name="clicked" handler="_on_reject_file_request" swapped="no"/> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">process-stop-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="name">request</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkButton"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="tooltip-text" translatable="yes">Open Folder</property> + <property name="valign">start</property> + <signal name="clicked" handler="_on_open_folder" swapped="no"/> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">folder-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">Open</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="valign">start</property> + <signal name="clicked" handler="_on_open_file" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="name">complete</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">Show Transfers</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="halign">end</property> + <signal name="clicked" handler="_on_show_transfers" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="error_label"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">end</property> + <property name="label"><error label></property> + <property name="wrap">True</property> + <property name="max-width-chars">20</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="name">error</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">Try Again</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="halign">end</property> + <signal name="clicked" handler="_on_bad_hash_retry" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">end</property> + <property name="label" translatable="yes">File corrupted during transfer</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="name">hash-error</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">end</property> + <property name="label" translatable="yes">You cancelled the transfer</property> + <property name="wrap">True</property> + <property name="max-width-chars">20</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">Show Transfers</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="halign">end</property> + <signal name="clicked" handler="_on_show_transfers" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="name">rejected</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="progress_label"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkProgressBar" id="progress_bar"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">Cancel</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="halign">end</property> + <signal name="clicked" handler="_on_cancel_transfer" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="name">progress</property> + <property name="position">5</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <style> + <class name="file-transfer"/> + </style> + </object> +</interface>
===================================== gajim/gtk/controls/base.py ===================================== @@ -283,9 +283,14 @@ def process_event(self, event): 'jingle-disconnected-received', 'jingle-error-received' ] - if self.is_chat and event.name in jingle_av_events: - self._process_jingle_av_event(event) - return + + if self.is_chat: + if event.name in jingle_av_events: + self._process_jingle_av_event(event) + return + if event.name in ('file-request-received', 'file-request-sent'): + self.add_jingle_file_transfer(event) + return
method_name = event.name.replace('-', '_') method_name = f'_on_{method_name}' @@ -1081,6 +1086,9 @@ def add_info_message(self, text): def add_file_transfer(self, transfer): self.conversation_view.add_file_transfer(transfer)
+ def add_jingle_file_transfer(self, event): + self.conversation_view.add_jingle_file_transfer(event) + def add_call_message(self, timestamp, text, event=None): self.conversation_view.add_call_message(timestamp, text, event)
===================================== gajim/gtk/controls/chat.py ===================================== @@ -26,13 +26,11 @@ from typing import ClassVar # pylint: disable=unused-import from typing import Type # pylint: disable=unused-import
-import os import time import logging
from gi.repository import Gtk from gi.repository import Gio -from gi.repository import Pango from gi.repository import GLib from gi.repository import Gdk
@@ -45,7 +43,6 @@ from gajim.common.helpers import AdditionalDataDict from gajim.common.helpers import open_uri from gajim.common.helpers import geo_provider_from_location -from gajim.common.helpers import open_file from gajim.common.const import AvatarSize from gajim.common.const import KindConstant from gajim.common.const import PEPEventType @@ -77,7 +74,6 @@ class ChatControl(BaseControl): A control for standard 1-1 chat """ _type = ControlType.CHAT - old_msg_kind = None # last kind of the printed message
# Set a command host to bound to. Every command given through a chat will be # processed with this command host. @@ -136,22 +132,6 @@ def __init__(self, account, jid): self.on_location_eventbox_leave_notify_event) self.handlers[id_] = widget
- self.info_bar = Gtk.InfoBar() - content_area = self.info_bar.get_content_area() - self.info_bar_label = Gtk.Label() - self.info_bar_label.set_use_markup(True) - self.info_bar_label.set_halign(Gtk.Align.START) - self.info_bar_label.set_valign(Gtk.Align.START) - self.info_bar_label.set_ellipsize(Pango.EllipsizeMode.END) - content_area.add(self.info_bar_label) - self.info_bar.set_no_show_all(True) - - self.xml.textview_box.pack_start(self.info_bar, False, True, 5) - self.xml.textview_box.reorder_child(self.info_bar, 1) - - # List of waiting infobar messages - self.info_bar_queue = [] - self.setup_seclabel() self.add_actions() self.update_ui() @@ -728,138 +708,3 @@ def _on_presence_received(self, event): status_line = _('%(name)s is now %(show)s %(status)s') % { 'name': name, 'show': uf_show, 'status': status} self.add_info_message(status_line) - - def _info_bar_show_message(self): - if self.info_bar.get_visible(): - # A message is already shown - return - if not self.info_bar_queue: - return - markup, buttons, _args, type_ = self.info_bar_queue[0] - self.info_bar_label.set_markup(markup) - - # Remove old buttons - area = self.info_bar.get_action_area() - for button in area.get_children(): - area.remove(button) - - # Add new buttons - for button in buttons: - self.info_bar.add_action_widget(button, 0) - - self.info_bar.set_message_type(type_) - self.info_bar.set_no_show_all(False) - self.info_bar.show_all() - - def _add_info_bar_message(self, markup, buttons, args, - type_=Gtk.MessageType.INFO): - self.info_bar_queue.append((markup, buttons, args, type_)) - self._info_bar_show_message() - - def _get_file_props_event(self, file_props, type_): - evs = app.events.get_events(self.account, self.contact.jid, [type_]) - for ev in evs: - if ev.file_props == file_props: - return ev - return None - - def _on_accept_file_request(self, _widget, file_props): - app.interface.instances['file_transfers'].on_file_request_accepted( - self.account, self.contact, file_props) - ev = self._get_file_props_event(file_props, 'file-request') - if ev: - app.events.remove_events(self.account, self.contact.jid, event=ev) - - def _on_cancel_file_request(self, _widget, file_props): - self._client.get_module('Bytestream').send_file_rejection(file_props) - ev = self._get_file_props_event(file_props, 'file-request') - if ev: - app.events.remove_events(self.account, self.contact.jid, event=ev) - - def _got_file_request(self, file_props): - """ - Show an InfoBar on top of control - """ - if app.settings.get('use_kib_mib'): - units = GLib.FormatSizeFlags.IEC_UNITS - else: - units = GLib.FormatSizeFlags.DEFAULT - - markup = '<b>%s</b>\n%s' % (_('File Transfer'), file_props.name) - if file_props.desc: - markup += '\n(%s)' % file_props.desc - markup += '\n%s: %s' % ( - _('Size'), - GLib.format_size_full(file_props.size, units)) - button_decline = Gtk.Button.new_with_mnemonic(_('_Decline')) - button_decline.connect( - 'clicked', self._on_cancel_file_request, file_props) - button_accept = Gtk.Button.new_with_mnemonic(_('_Accept')) - button_accept.connect( - 'clicked', self._on_accept_file_request, file_props) - self._add_info_bar_message( - markup, - [button_decline, button_accept], - file_props, - Gtk.MessageType.QUESTION) - - def _on_open_ft_folder(self, _widget, file_props): - path = os.path.split(file_props.file_name)[0] - if os.path.exists(path) and os.path.isdir(path): - open_file(path) - ev = self._get_file_props_event(file_props, 'file-completed') - if ev: - app.events.remove_events(self.account, self.contact.jid, event=ev) - - def _on_ok(self, _widget, file_props, type_): - ev = self._get_file_props_event(file_props, type_) - if ev: - app.events.remove_events(self.account, self.contact.jid, event=ev) - - def _got_file_completed(self, file_props): - markup = '<b>%s</b>\n%s' % (_('File Transfer Completed'), - file_props.name) - if file_props.desc: - markup += '\n(%s)' % file_props.desc - b1 = Gtk.Button.new_with_mnemonic(_('Open _Folder')) - b1.connect('clicked', self._on_open_ft_folder, file_props) - b2 = Gtk.Button.new_with_mnemonic(_('_Close')) - b2.connect('clicked', self._on_ok, file_props, 'file-completed') - self._add_info_bar_message( - markup, - [b1, b2], - file_props) - - def _got_file_error(self, file_props, type_, pri_txt, sec_txt): - markup = '<b>%s</b>\n%s' % (pri_txt, sec_txt) - button = Gtk.Button.new_with_mnemonic(_('_Close')) - button.connect('clicked', self._on_ok, file_props, type_) - self._add_info_bar_message( - markup, - [button], - file_props, - Gtk.MessageType.ERROR) - - def on_event_added(self, event): - if event.account != self.account: - return - if event.jid != self.contact.jid: - return - if event.type_ == 'file-request': - self._got_file_request(event.file_props) - elif event.type_ == 'file-completed': - self._got_file_completed(event.file_props) - elif event.type_ in ('file-error', 'file-stopped'): - msg_err = '' - if event.file_props.error == -1: - msg_err = _('Remote contact stopped transfer') - elif event.file_props.error == -6: - msg_err = _('Error opening file') - self._got_file_error(event.file_props, event.type_, - _('File transfer stopped'), msg_err) - elif event.type_ in ('file-request-error', 'file-send-error'): - self._got_file_error( - event.file_props, - event.type_, - _('File transfer cancelled'), - _('Connection with peer cannot be established.'))
===================================== gajim/gtk/conversation/rows/file_transfer.py ===================================== @@ -27,6 +27,7 @@ from ...dialogs import ErrorDialog from ...util import EventHelper from ...util import get_builder +from ...util import format_eta
class FileTransferRow(BaseRow, EventHelper): @@ -126,7 +127,7 @@ def _on_transfer_progress(self, transfer, _signal_name): if bytes_sec == 0: eta = '∞' else: - eta = self._format_eta(round( + eta = format_eta(round( (transfer.size - transfer.seen) / bytes_sec))
progress = float(transfer.seen) / transfer.size @@ -136,14 +137,3 @@ def _on_transfer_progress(self, transfer, _signal_name): 'time': eta})
self._ui.progress_bar.set_fraction(progress) - - @staticmethod - def _format_eta(time_): - times = {'minutes': 0, 'seconds': 0} - time_ = int(time_) - times['seconds'] = time_ % 60 - if time_ >= 60: - time_ /= 60 - times['minutes'] = round(time_ % 60) - return _('%(minutes)s min %(seconds)s sec') % times - return _('%s sec') % times['seconds']
===================================== gajim/gtk/conversation/rows/file_transfer_jingle.py ===================================== @@ -0,0 +1,235 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see http://www.gnu.org/licenses/. + +import os +import time +from datetime import datetime + +from gi.repository import GLib +from gi.repository import Gtk + +from gajim.common import app +from gajim.common import ged +from gajim.common.const import AvatarSize +from gajim.common.helpers import open_file +from gajim.common.i18n import _ + +from .base import BaseRow +from ...util import get_builder +from ...util import format_eta + + +class FileTransferJingleRow(BaseRow): + def __init__(self, account, contact, event): + BaseRow.__init__(self, account) + + self.type = 'file-transfer' + timestamp = time.time() + self.timestamp = datetime.fromtimestamp(timestamp) + self.db_timestamp = timestamp + + self._contact = contact + self._file_props = event.file_props + self._start_time = 0 + + if app.settings.get('use_kib_mib'): + self._units = GLib.FormatSizeFlags.IEC_UNITS + else: + self._units = GLib.FormatSizeFlags.DEFAULT + + avatar_placeholder = Gtk.Box() + avatar_placeholder.set_size_request(AvatarSize.ROSTER, -1) + self.grid.attach(avatar_placeholder, 0, 0, 1, 1) + + timestamp_widget = self.create_timestamp_widget(self.timestamp) + timestamp_widget.set_hexpand(True) + timestamp_widget.set_halign(Gtk.Align.END) + timestamp_widget.set_valign(Gtk.Align.START) + self.grid.attach(timestamp_widget, 2, 0, 1, 1) + + self._ui = get_builder('file_transfer_jingle.ui') + self.grid.attach(self._ui.transfer_box, 1, 0, 1, 1) + + self._ui.connect_signals(self) + + # pylint: disable=line-too-long + app.ged.register_event_handler('file-completed', ged.GUI1, self.process_event) + app.ged.register_event_handler('file-hash-error', ged.GUI1, self.process_event) + app.ged.register_event_handler('file-send-error', ged.GUI1, self.process_event) + app.ged.register_event_handler('file-request-error', ged.GUI1, self.process_event) + app.ged.register_event_handler('file-progress', ged.GUI1, self.process_event) + app.ged.register_event_handler('file-error', ged.GUI1, self.process_event) + app.ged.register_event_handler('jingle-error-received', ged.GUI1, self.process_event) + app.ged.register_event_handler('jingle-ft-cancelled-received', ged.GUI1, self.process_event) + # pylint: enable=line-too-long + + self.show_all() + + self._display_transfer_info(event) + + def _display_transfer_info(self, event): + if event.name == 'file-request-sent': + self._ui.action_stack.set_visible_child_name('progress') + self._ui.progress_label.set_text(_('Waiting…')) + + self._ui.transfer_action.set_text(_('File Offered…')) + + file_name = GLib.markup_escape_text(self._file_props.name) + if self._file_props.mime_type: + file_name = f'{file_name} ({self._file_props.mime_type})' + self._ui.file_name.set_text(file_name) + self._ui.file_name.set_tooltip_text(file_name) + + if self._file_props.desc: + desc = GLib.markup_escape_text(self._file_props.desc) + self._ui.file_description.set_text(desc) + self._ui.file_description.set_tooltip_text(desc) + else: + self._ui.file_description.hide() + + self._ui.file_size.set_text( + GLib.format_size_full(self._file_props.size, self._units)) + + def process_event(self, event): + if event.name == 'jingle-error-received': + if event.sid != self._file_props.sid: + return + self._ui.action_stack.set_visible_child_name('error') + self._ui.transfer_action.set_text(_('File Transfer Cancelled')) + self._ui.error_label.set_text(event.reason) + return + + if event.name == 'jingle-ft-cancelled-received': + if event.sid != self._file_props.sid: + return + self._ui.action_stack.set_visible_child_name('error') + self._ui.transfer_action.set_text(_('File Transfer Cancelled')) + self._ui.error_label.set_text( + _('%(name)s cancelled the transfer (%(reason)s)') % { + 'name': self._contact.name, + 'reason': event.reason}) + return + + if event.file_props.sid != self._file_props.sid: + return + + if event.name == 'file-completed': + self._show_completed() + elif event.name == 'file-error': + self._show_error(event) + elif event.name == 'file-hash-error': + self._ui.action_stack.set_visible_child_name('hash-error') + self._ui.transfer_action.set_text(_('File Verification Failed')) + elif event.name in ('file-request-error', 'file-send-error'): + self._ui.action_stack.set_visible_child_name('error') + self._ui.transfer_action.set_text(_('File Transfer Cancelled')) + error_text = _('Connection with %s could not be ' + 'established.') % self._contact.name + if event.error_msg: + error_text = f'{error_text} ({event.error_msg})' + self._ui.error_label.set_text(error_text) + + elif event.name == 'file-progress': + self._update_progress(event.file_props) + + def _update_progress(self, file_props): + self._ui.action_stack.set_visible_child_name('progress') + self._ui.transfer_action.set_text(_('Transferring File…')) + + time_now = time.time() + full_size = file_props.size + + if file_props.type_ == 's': + # We're sending a file + if self._start_time == 0: + self._start_time = time.time() + transferred_size = file_props.transfered_size[-1][1] + else: + # We're receiving a file + transferred_size = file_props.received_len + + if full_size == 0: + return + + bytes_sec = round(transferred_size / (time_now - self._start_time), 1) + speed = f'{GLib.format_size_full(bytes_sec, self._units)}/s' + self._ui.progress_label.set_tooltip_text(_('Speed: %s') % speed) + + if bytes_sec == 0: + eta = '∞' + else: + eta = format_eta(round( + (full_size - transferred_size) / bytes_sec)) + + progress = float(transferred_size) / full_size + self._ui.progress_label.set_text( + _('%(progress)s %% (%(time)s remaining)') % { + 'progress': round(progress * 100), + 'time': eta}) + + self._ui.progress_bar.set_fraction(progress) + + def _show_error(self, event): + self._ui.action_stack.set_visible_child_name('error') + self._ui.transfer_action.set_text(_('File Transfer Stopped')) + if event.file_props.error == -1: + self._ui.error_label.set_text( + _('%s stopped the transfer') % self._contact.name) + elif event.file_props.error == -6: + self._ui.error_label.set_text(_('Error opening file')) + elif event.file_props.error == -12: + self._ui.error_label.set_text(_('SSL certificate error')) + else: + self._ui.error_label.set_text(_('An error occurred')) + + def _show_completed(self): + self._ui.action_stack.set_visible_child_name('complete') + self._ui.transfer_action.set_text(_('File Transfer Completed')) + + def _on_accept_file_request(self, _widget): + app.interface.instances['file_transfers'].on_file_request_accepted( + self._account, self._contact, self._file_props) + self._start_time = time.time() + + def _on_reject_file_request(self, _widget): + self._client.get_module('Bytestream').send_file_rejection( + self._file_props) + self._ui.action_stack.set_visible_child_name('rejected') + self._ui.transfer_action.set_text(_('File Transfer Cancelled')) + + def _on_open_file(self, _widget): + if os.path.exists(self._file_props.file_name): + open_file(self._file_props.file_name) + + def _on_open_folder(self, _widget): + path = os.path.split(self._file_props.file_name)[0] + if os.path.exists(path) and os.path.isdir(path): + open_file(path) + + def _on_bad_hash_retry(self, _widget): + app.interface.instances['file_transfers'].show_hash_error( + self._contact.jid, + self._file_props, + self._account) + + def _on_cancel_transfer(self, _widget): + app.interface.instances['file_transfers'].cancel_transfer( + self._file_props) + + def _on_show_transfers(self, _widget): + file_transfers = app.interface.instances['file_transfers'] + if file_transfers.window.get_property('visible'): + file_transfers.window.present() + else: + file_transfers.window.show_all()
===================================== gajim/gtk/conversation/view.py ===================================== @@ -32,6 +32,7 @@ from .rows.call import CallRow from .rows.date import DateRow from .rows.file_transfer import FileTransferRow +from .rows.file_transfer_jingle import FileTransferJingleRow from .rows.muc_subject import MUCSubject from .rows.muc_join_left import MUCJoinLeft from .rows.muc_user_status import MUCUserStatus @@ -179,6 +180,11 @@ def add_file_transfer(self, transfer): transfer_row = FileTransferRow(self._account, transfer) self._insert_message(transfer_row)
+ def add_jingle_file_transfer(self, event): + jingle_transfer_row = FileTransferJingleRow( + self._account, self._contact, event) + self._insert_message(jingle_transfer_row) + def add_call_message(self, timestamp, text, event): call_row = CallRow(self._account, self._contact,
===================================== gajim/gtk/filetransfer.py ===================================== @@ -29,27 +29,26 @@ from gi.repository import GLib from gi.repository import Pango
-from nbxmpp import JID - from gajim.common import app +from gajim.common import ged from gajim.common import helpers from gajim.common.i18n import _ from gajim.common.file_props import FilesProp from gajim.common.helpers import open_file from gajim.common.helpers import file_is_locked -from gajim.common.modules.bytestream import (is_transfer_active, - is_transfer_paused, - is_transfer_stopped) +from gajim.common.modules.bytestream import is_transfer_active +from gajim.common.modules.bytestream import is_transfer_paused +from gajim.common.modules.bytestream import is_transfer_stopped +from gajim.common.nec import NetworkEvent
from .dialogs import DialogButton from .dialogs import ConfirmationDialog -from .dialogs import HigDialog -from .dialogs import InformationDialog from .dialogs import ErrorDialog from .filechoosers import FileSaveDialog from .file_transfer_send import SendFileDialog from .tooltips import FileTransfersTooltip from .util import get_builder +from .util import format_eta
log = logging.getLogger('gajim.gui.filetransfer')
@@ -71,6 +70,8 @@ def __init__(self): self.files_props = {'r': {}, 's': {}} self.height_diff = 0
+ self._last_progress_update = 0 + self._ui = get_builder('filetransfers.ui') self.window = self._ui.file_transfers_window show_notification = app.settings.get('notify_on_file_complete') @@ -159,6 +160,115 @@ def __init__(self):
self._ui.connect_signals(self)
+ # pylint: disable=line-too-long + app.ged.register_event_handler('file-completed', ged.GUI1, self._file_completed) + app.ged.register_event_handler('file-hash-error', ged.GUI1, self._file_hash_error) + app.ged.register_event_handler('file-send-error', ged.GUI1, self._file_send_error) + app.ged.register_event_handler('file-request-error', ged.GUI1, self._file_request_error) + app.ged.register_event_handler('file-request-received', ged.GUI1, self._file_request_received) + app.ged.register_event_handler('file-progress', ged.GUI1, self._file_progress) + app.ged.register_event_handler('file-error', ged.GUI1, self._file_error) + app.ged.register_event_handler('jingle-ft-cancelled-received', ged.GUI1, self._file_transfer_cancelled) + # pylint: enable=line-too-long + + def _file_completed(self, event): + self.set_status(event.file_props, 'ok') + self.set_progress( + event.file_props.type_, + event.file_props.sid, + event.file_props.received_len) + + def _file_error(self, event): + self.set_status(event.file_props, 'stop') + + def _file_hash_error(self, event): + self.set_status(event.file_props, 'hash_error') + if self.window.get_property('visible'): + self.show_hash_error(event.jid, event.file_props, event.account) + + def _file_send_error(self, event): + self.set_status(event.file_props, 'stop') + + if helpers.allow_showing_notification(event.account): + if app.window.is_chat_active(event.account, event.jid): + return + app.notification.popup( + _('File Transfer Error'), + event.jid, + event.account, + 'file-send-error', + 'dialog-error', + _('File Transfer Error'), + event.file_props.name) + + def _file_request_error(self, event): + self.set_status(event.file_props, 'stop') + errno = event.file_props.error + account = event.conn.name + + if self.window.get_property('visible'): + if errno in (-4, -5): + self.show_stopped(event.jid, event.file_props, event.error_msg) + return + + if errno in (-4, -5): + msg_type = 'file-error' + else: + msg_type = 'file-request-error' + + if helpers.allow_showing_notification(account): + if app.window.is_chat_active(account, event.jid): + return + app.notification.popup( + _('File Transfer Error'), + event.jid, + account, + msg_type, + 'dialog-error', + title=_('File Transfer Error'), + text=event.file_props.name) + + def _file_request_received(self, event): + account = event.conn.name + client = app.get_client(account) + contact = client.get_module('Contacts').get_contact(event.jid) + + if event.file_props.session_type == 'jingle': + jingle = event.stanza.getTag('jingle') + content = jingle.getTag('content') + description = content.getTag('description') + request = description.getTag('request') + if request: + # If we get a request instead + self.add_transfer(account, contact, event.file_props) + return + + if helpers.allow_showing_notification(account): + if app.window.is_chat_active(event.account, event.jid): + return + app.notification.popup( + _('File Transfer Request'), + event.jid, + account, + 'file-request-received', + icon_name='document-send', + title=_('File Transfer Request'), + text=_('%s wants to send you a file') % contact.name) + + def _file_transfer_cancelled(self, event): + file_props = None + # get the file_props of our session + file_props = FilesProp.getFileProp(event.conn.name, event.sid) + if not file_props: + return + + self.set_status(file_props, 'stop') + file_props.error = -4 # is it the right error code? + if self.window.get_property('visible'): + self.show_stopped(event.jid, + file_props, + 'Peer cancelled the transfer') + def _query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip): try: x_pos, y_pos = widget.convert_widget_to_bin_window_coords( @@ -207,85 +317,6 @@ def find_transfer_by_jid(self, account, jid): raise Exception('file_props has no type') return active_transfers
- def show_completed(self, jid, file_props): - """ - Show a dialog saying that file (file_props) has been transferred - """ - def on_open(widget, file_props): - dialog.destroy() - if not file_props.file_name: - return - path = os.path.split(file_props.file_name)[0] - if os.path.exists(path) and os.path.isdir(path): - open_file(path) - self._ui.transfers_list.get_selection().unselect_all() - - if file_props.type_ == 'r': - # file path is used below in 'Save in' - (file_path, file_name) = os.path.split(file_props.file_name) - else: - file_name = file_props.name - sectext = _('File name: %s') % GLib.markup_escape_text(file_name) - sectext += '\n' + _('Size: %s') % GLib.format_size_full( - file_props.size, self.units) - client = app.get_client(file_props.tt_account) - if file_props.type_ == 'r': - jid = JID.from_string(file_props.sender) - sender_name = client.get_module('Contacts').get_contact( - jid.bare).name - sender = sender_name - else: - # You is a reply of who sent a file - sender = _('You') - sectext += '\n' + _('Sender: %s') % sender - sectext += '\n' + _('Recipient: ') - if file_props.type_ == 's': - jid = JID.from_string(file_props.receiver) - receiver_name = client.get_module('Contacts').get_contact( - jid.bare).name - recipient = receiver_name - else: - # You is a reply of who received a file - recipient = _('You') - sectext += recipient - if file_props.type_ == 'r': - sectext += '\n' + _('Saved in: %s') % file_path - - dialog = HigDialog(app.window, - Gtk.MessageType.INFO, - Gtk.ButtonsType.NONE, - _('File transfer completed'), - sectext) - if file_props.type_ == 'r': - button = Gtk.Button.new_with_mnemonic(_('Open _Folder')) - button.connect('clicked', on_open, file_props) - dialog.action_area.pack_start(button, True, True, 0) - ok_button = dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) - - def on_ok(widget): - dialog.destroy() - ok_button.connect('clicked', on_ok) - dialog.show_all() - - def show_request_error(self, file_props): - """ - Show error dialog to the recipient saying that - transfer has been canceled - """ - InformationDialog( - _('File transfer cancelled'), - _('Connection with peer could not be established.')) - self._ui.transfers_list.get_selection().unselect_all() - - def show_send_error(self, file_props): - """ - Show error dialog to the sender saying that transfer has been canceled - """ - InformationDialog( - _('File transfer cancelled'), - _('Connection with peer could not be established.')) - self._ui.transfers_list.get_selection().unselect_all() - def show_stopped(self, jid, file_props, error_msg=''): if file_props.type_ == 'r': file_name = os.path.basename(file_props.file_name) @@ -362,6 +393,11 @@ def send_file(self, account, contact, resource_jid, file_path, client = app.get_client(account) client.get_module('Jingle').start_file_transfer( str(resource_jid), file_props) + app.nec.push_incoming_event( + NetworkEvent('file-request-sent', + file_props=file_props, + account=account, + jid=contact.jid)) self.add_transfer(account, contact, file_props)
return True @@ -391,38 +427,6 @@ def _on_accepted(account, contact, file_props, file_path): 'not have permission to overwrite it.')) return
- stat = os.stat(file_path) - dl_size = stat.st_size - file_size = file_props.size - dl_finished = dl_size >= file_size - - def _on_resume(): - if not dl_finished: - file_props.offset = dl_size - self._start_receive( - file_path, account, contact, file_props) - - def _on_replace(): - self._start_receive( - file_path, account, contact, file_props) - - def _on_cancel(): - client.get_module('Bytestream').send_file_rejection( - file_props) - - ConfirmationDialog( - _('File Transfer Conflict'), - _('File already exists'), - _('Resume download or replace file?'), - [DialogButton.make('Cancel', - callback=_on_cancel), - DialogButton.make('OK', - text=_('Resume _Download'), - callback=_on_resume), - DialogButton.make('Accept', - text=_('Replace _File'), - callback=_on_replace)]).show() - # File does not exist yet dirname = os.path.dirname(file_path) if not os.access(dirname, os.W_OK) and os.name != 'nt': @@ -446,40 +450,6 @@ def _on_cancel(): path=app.settings.get('last_save_dir'), file_name=file_props.name)
- def show_file_request(self, account, contact, file_props): - """ - Show dialog asking for comfirmation and store location of new file - requested by a contact - """ - if not file_props or not file_props.name: - return - - sectext = _('File: %s') % GLib.markup_escape_text( - file_props.name) - if file_props.size: - sectext += '\n' + _('Size: %s') % GLib.format_size_full( - file_props.size, self.units) - if file_props.mime_type: - sectext += '\n' + _('Type: %s') % file_props.mime_type - if file_props.desc: - sectext += '\n' + _('Description: %s') % file_props.desc - - def _on_ok(): - self.on_file_request_accepted(account, contact, file_props) - - def _on_cancel(): - app.get_client(account).get_module( - 'Bytestream').send_file_rejection(file_props) - - ConfirmationDialog( - _('File Transfer Request'), - _('%s wants to send you a file') % contact.name, - sectext, - [DialogButton.make('Cancel', - callback=_on_cancel), - DialogButton.make('Accept', - callback=_on_ok)]).show() - def set_status(self, file_props, status): """ Change the status of a transfer to state 'status' @@ -540,21 +510,6 @@ def _format_percent(self, percent): _str += str(percent) + '% \n' return _str
- def _format_time(self, _time): - times = {'hours': 0, 'minutes': 0, 'seconds': 0} - _time = int(_time) - times['seconds'] = _time % 60 - if _time >= 60: - _time /= 60 - times['minutes'] = _time % 60 - if _time >= 60: - times['hours'] = _time / 60 - - # Print remaining time in format 00:00:00 - # You can change the places of (hours), (minutes), (seconds) - - # they are not translatable. - return _('%(hours)02.d:%(minutes)02.d:%(seconds)02.d') % times - def _get_eta_and_speed(self, full_size, transfered_size, file_props): if not file_props.transfered_size: return 0., 0. @@ -588,31 +543,31 @@ def _remove_transfer(self, iter_, sid, file_props): account = file_props.tt_account if account in app.connections: # there is a connection to the account - app.get_client(account).get_module( - 'Bytestream').remove_transfer(file_props) - if file_props.type_ == 'r': # we receive a file - other = file_props.sender - else: # we send a file - other = file_props.receiver - if isinstance(other, str): - jid = app.get_jid_without_resource(other) - else: # It's a Contact instance - jid = other.jid - for ev_type in ('file-error', 'file-completed', - 'file-request-error', 'file-send-error', - 'file-stopped'): - for event in app.events.get_events(account, jid, [ev_type]): - if event.file_props.sid == file_props.sid: - app.events.remove_events(account, jid, event) + client = app.get_client(account) + client.get_module('Bytestream').remove_transfer(file_props)
FilesProp.deleteFileProp(file_props) del file_props
+ def _file_progress(self, event): + self.set_progress( + event.file_props.type_, + event.file_props.sid, + event.file_props.received_len) + def set_progress(self, typ, sid, transfered_size, iter_=None): """ Change the progress of a transfer with new transfered size """ + if time.time() - self._last_progress_update < 0.5: + # Update window every 500ms only + return + self._last_progress_update = time.time() + file_props = FilesProp.getFilePropByType(typ, sid) + if file_props is None: + return + full_size = file_props.size if full_size == 0: percent = 0 @@ -647,7 +602,7 @@ def set_progress(self, typ, sid, transfered_size, iter_=None):
self.model.set(iter_, Column.PROGRESS, text) self.model.set(iter_, Column.PERCENT, int(percent)) - text = self._format_time(eta) + text = format_eta(eta) text += '\n' # This should make the string KB/s, # where 'KB' part is taken from %s. @@ -734,6 +689,7 @@ def add_transfer(self, account, contact, file_props): """ if file_props is None: return + file_props.elapsed_time = 0 iter_ = self.model.prepend() if file_props.type_ == 'r': @@ -766,7 +722,6 @@ def add_transfer(self, account, contact, file_props): file_props.tt_account = account self.set_status(file_props, status) self._set_cleanup_sensitivity() - self.window.show_all()
def _on_transfers_list_row_activated(self, widget, path, col): # try to open the containing folder @@ -906,6 +861,10 @@ def _on_cancel_button_clicked(self, widget): s_iter = selected[1] sid = self.model[s_iter][Column.SID] file_props = FilesProp.getFilePropByType(sid[0], sid[1:]) + self.cancel_transfer(file_props) + + def cancel_transfer(self, file_props): + # TODO: does not cancel transfer somehow account = file_props.tt_account if account not in app.connections: return
===================================== gajim/gtk/main.py ===================================== @@ -83,6 +83,8 @@ def __init__(self): ('jingle-connected-received', ged.GUI1, self._on_event), ('jingle-disconnected-received', ged.GUI1, self._on_event), ('jingle-error-received', ged.GUI1, self._on_event), + ('file-request-received', ged.GUI1, self._on_event), + ('file-request-sent', ged.GUI1, self._on_event), ('our-show', ged.GUI1, self._on_our_show), ('signed-in', ged.GUI1, self._on_signed_in), ('account-enabled', ged.GUI1, self._on_account_enabled), @@ -553,7 +555,10 @@ def _on_event(self, event): content_types.append(item.media) if 'audio' in content_types or 'video' in content_types: # AV Call received, open chat control - self.add_chat(event.account, event.jid, 'contact') + self.add_chat(event.account, event.jid, 'contact') + elif event.name == 'file-request-received': + # Jingle file transfer, open chat control + self.add_chat(event.account, event.jid, 'contact') else: # No chat is open, dont handle any gui events return
===================================== gajim/gtk/preferences.py ===================================== @@ -416,19 +416,10 @@ def __init__(self, *args): props={'entries': trayicon_items}, callback=self._on_trayicon),
- # TODO: - Setting(SettingKind.SWITCH, - _('Open Events'), - SettingType.CONFIG, - 'autopopup', - desc=_('Open events instead of showing a notification ' - 'in the contact list')), - Setting(SettingKind.NOTIFICATIONS, _('Show Notifications'), SettingType.DIALOG, props={'dialog': NotificationsDialog}), - ]
PreferenceBox.__init__(self, settings)
===================================== gajim/gtk/status_icon.py ===================================== @@ -122,7 +122,7 @@ def _on_unread_count_changed(self, _chat_list_stack, _workspace_id, _count): count = app.window.get_total_unread_count() self.update_icon(count=count)
- def update_icon(self, count=0): + def update_icon(self, *args, count=0): if not app.interface.systray_enabled: return if app.settings.get('trayicon') == 'always':
===================================== gajim/gtk/util.py ===================================== @@ -618,6 +618,17 @@ def get_account_location_icon_name(account): return None if location is None else 'applications-internet'
+def format_eta(time_): + times = {'minutes': 0, 'seconds': 0} + time_ = int(time_) + times['seconds'] = time_ % 60 + if time_ >= 60: + time_ /= 60 + times['minutes'] = round(time_ % 60) + return _('%(minutes)s min %(seconds)s s') % times + return _('%s s') % times['seconds'] + + def format_fingerprint(fingerprint): fplen = len(fingerprint) wordsize = fplen // 8
===================================== gajim/gui_interface.py ===================================== @@ -52,7 +52,6 @@ from nbxmpp import JID
from gajim.common import app -from gajim.common import events from gajim.common.dbus import logind from gajim.common.dbus import music_track
@@ -74,7 +73,6 @@ from gajim.common.preview import PreviewManager from gajim.common.const import Display
-from gajim.common.file_props import FilesProp from gajim.common.connection_handlers_events import InformationEvent
from gajim.common import ged @@ -127,6 +125,9 @@ def __init__(self): self.handle_event_file_rcv_completed, self.handle_event_file_progress, self.handle_event_file_error) + + self._last_ft_progress_update = 0 + app.proxy65_manager = proxy65_manager.Proxy65Manager(app.idlequeue)
app.nec = NetworkEventsController() @@ -185,8 +186,6 @@ def __init__(self): from gajim.gui.emoji_chooser import emoji_chooser emoji_chooser.load()
- self._last_ftwindow_update = 0 - self._network_monitor = Gio.NetworkMonitor.get_default() self._network_monitor.connect('notify::network-available', self._network_status_changed) @@ -211,10 +210,6 @@ def _create_core_handlers_list(self): 'read-state-sync': [self.handle_event_read_state_sync], 'metacontacts-received': [self.handle_event_metacontacts], 'roster-item-exchange-received': [self.handle_event_roster_item_exchange], - 'file-send-error': [self.handle_event_file_send_error], - 'file-request-error': [self.handle_event_file_request_error], - 'file-request-received': [self.handle_event_file_request], - 'jingle-ft-cancelled-received': [self.handle_event_jingleft_cancel], } # pylint: enable=line-too-long
@@ -235,25 +230,8 @@ def _register_core_handlers(self): prio, event_handler)
- def add_event(self, account, jid, event): - """ - Add an event to the app.events var - """ - app.events.add_event(account, jid, event) - - # TODO: set urgency hint; show unread count in window title? - def handle_event(self, account, jid, type_): jid = JID.from_string(jid) - file_event_types = [ - 'file-request', - 'file-request-error', - 'file-send-error', - 'file-error', - 'file-stopped', - 'file-completed', - 'file-hash-error' - ]
if type_ in ('connection-lost', 'connection-failed'): app.window.show_account_page(account) @@ -268,8 +246,6 @@ def handle_event(self, account, jid, type_): app.events.remove_events(account, jid, types=type_) elif type_ == 'jingle-incoming': app.window.select_chat(account, jid.bare) - elif type_ in file_event_types: - self._handle_event_jingle_file(account, jid, type_) elif type_ in ('subscription-request', 'unsubscribed', 'gc-invitation'): @@ -277,46 +253,6 @@ def handle_event(self, account, jid, type_):
app.window.present()
- @staticmethod - def _handle_event_jingle_file(account, jid, type_): - event = app.events.get_first_event(account, jid, type_) - if not event: - return - - file_transfers = app.interface.instances['file_transfers'] - if event.type_ == 'file-request': - client = app.get_client(account) - contact = client.get_module('Contacts').get_contact(jid) - file_transfers.show_file_request(account, contact, event.file_props) - app.events.remove_events(account, jid, event) - return - - if event.type_ in ('file-request-error', 'file-send-error'): - file_transfers.show_send_error(event.file_props) - app.events.remove_events(account, jid, event) - return - - if event.type_ in ('file-error', 'file-stopped'): - msg_err = '' - if event.file_props.error == -1: - msg_err = _('Remote contact stopped transfer') - elif event.file_props.error == -6: - msg_err = _('Error opening file') - file_transfers.show_stopped( - jid, event.file_props, error_msg=msg_err) - app.events.remove_events(account, jid, event) - return - - if event.type_ == 'file-hash-error': - file_transfers.show_hash_error(jid, event.file_props, account) - app.events.remove_events(account, jid, event) - return - - if event.type_ == 'file-completed': - file_transfers.show_completed(jid, event.file_props) - app.events.remove_events(account, jid, event) - return - @staticmethod def handle_event_information(event): if not event.popup: @@ -622,318 +558,104 @@ def handle_event_roster_item_exchange(event): jid_from=event.fjid)
# Jingle File Transfer - def handle_event_file_send_error(self, event): - file_transfers = self.instances['file_transfers'] - file_transfers.set_status(event.file_props, 'stop') - - if helpers.allow_popup_window(event.account): - file_transfers.show_send_error(event.file_props) - return - - event = events.FileSendErrorEvent(event.file_props) - self.add_event(event.account, event.jid, event) - - if helpers.allow_showing_notification(event.account): - event_type = _('File Transfer Error') - app.notification.popup( - event_type, - event.jid, - event.account, - 'file-send-error', - 'dialog-error', - event_type, - event.file_props.name) - - def handle_event_file_request_error(self, event): - # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg)) - file_transfers = self.instances['file_transfers'] - file_transfers.set_status(event.file_props, 'stop') - errno = event.file_props.error - account = event.conn.name - - if helpers.allow_popup_window(account): - if errno in (-4, -5): - file_transfers.show_stopped( - event.jid, event.file_props, event.error_msg) - else: - file_transfers.show_request_error(event.file_props) - return - - if errno in (-4, -5): - event_class = events.FileErrorEvent - msg_type = 'file-error' - else: - event_class = events.FileRequestErrorEvent - msg_type = 'file-request-error' - - file_event = event_class(event.file_props) - self.add_event(account, event.jid, file_event) - - if helpers.allow_showing_notification(account): - # Check if we should be notified - event_type = _('File Transfer Error') - app.notification.popup( - event_type, - event.jid, - account, - msg_type, - 'dialog-error', - title=event_type, - text=event.file_props.name) - - def handle_event_file_request(self, event): - account = event.conn.name - client = app.get_client(account) - contact = client.get_module('Contacts').get_contact(event.jid) - - if event.file_props.session_type == 'jingle': - request = \ - event.stanza.getTag('jingle').getTag('content').getTag( - 'description').getTag('request') - if request: - # If we get a request instead - self.instances['file_transfers'].add_transfer( - account, contact, event.file_props) - return - if helpers.allow_popup_window(account): - self.instances['file_transfers'].show_file_request( - account, contact, event.file_props) - return - file_event = events.FileRequestEvent(event.file_props) - self.add_event(account, event.jid, file_event) - if helpers.allow_showing_notification(account): - txt = _('%s wants to send you a file.') % contact.name - event_type = _('File Transfer Request') - app.notification.popup( - event_type, - event.jid, - account, - 'file-request', - icon_name='document-send', - title=event_type, - text=txt) - @staticmethod def handle_event_file_error(title, message): + # TODO: integrate this better ErrorDialog(title, message)
def handle_event_file_progress(self, _account, file_props): - if time.time() - self._last_ftwindow_update > 0.5: - # Update ft window every 500ms - self._last_ftwindow_update = time.time() - self.instances['file_transfers'].set_progress( - file_props.type_, file_props.sid, file_props.received_len) - - def handle_event_jingleft_cancel(self, event): - file_transfers = self.instances['file_transfers'] - file_props = None - # get the file_props of our session - file_props = FilesProp.getFileProp(event.conn.name, event.sid) - if not file_props: + if time.time() - self._last_ft_progress_update < 0.5: + # Update progress every 500ms only return - file_transfers.set_status(file_props, 'stop') - file_props.error = -4 # is it the right error code? - file_transfers.show_stopped( - event.jid, - file_props, - 'Peer cancelled the transfer') + + self._last_ft_progress_update = time.time() + app.nec.push_incoming_event( + NetworkEvent('file-progress', file_props=file_props))
def handle_event_file_rcv_completed(self, account, file_props): - file_transfers = self.instances['file_transfers'] - if file_props.error == 0: - file_transfers.set_progress( - file_props.type_, file_props.sid, file_props.received_len) - jid = JID.from_string(file_props.receiver) - app.nec.push_incoming_event( - NetworkEvent('file-transfer-completed', - file_props=file_props, - jid=jid.bare)) + jid = JID.from_string(file_props.receiver) + if file_props.error != 0: + self.instances['file_transfers'].set_status(file_props, 'stop')
- else: - file_transfers.set_status(file_props, 'stop') if (not file_props.completed and ( file_props.stalled or file_props.paused)): return
if file_props.type_ == 'r': # We receive a file app.socks5queue.remove_receiver(file_props.sid, True, True) - if file_props.session_type == 'jingle': - if file_props.hash_ and file_props.error == 0: - # We compare hashes in a new thread - self.hashThread = Thread(target=self.__compare_hashes, - args=(account, file_props)) - self.hashThread.start() + if file_props.session_type != 'jingle': + return + + if file_props.hash_ and file_props.error == 0: + # We compare hashes in a new thread + self.hashThread = Thread( + target=self.__compare_hashes, + args=(account, file_props)) + self.hashThread.start() + else: + # We didn't get the hash, sender probably doesn't support that + if file_props.error == 0: + app.nec.push_incoming_event( + NetworkEvent('file-completed', + file_props=file_props, + jid=jid.bare)) else: - # We didn't get the hash, sender probably doesn't - # support that - jid = file_props.sender - self._popup_ft_result(account, jid, file_props) - if file_props.error == 0: - file_transfers.set_status(file_props, 'ok') - client = app.get_client(account) - session = client.get_module('Jingle').get_jingle_session( - jid=None, sid=file_props.sid) - # End jingle session - # TODO: Only if there are no other parallel downloads in - # this session - if session: - session.end_session() + app.nec.push_incoming_event( + NetworkEvent('file-error', + file_props=file_props, + jid=jid.bare)) + + # End jingle session + # TODO: Only if there are no other parallel downloads in + # this session + client = app.get_client(account) + session = client.get_module('Jingle').get_jingle_session( + jid=None, sid=file_props.sid) + if session: + session.end_session() else: # We send a file - jid = file_props.receiver app.socks5queue.remove_sender(file_props.sid, True, True) - self._popup_ft_result(account, jid, file_props) + if file_props.error == 0: + app.nec.push_incoming_event( + NetworkEvent('file-completed', + file_props=file_props, + jid=jid.bare)) + else: + app.nec.push_incoming_event( + NetworkEvent('file-error', + file_props=file_props, + jid=jid.bare))
def __compare_hashes(self, account, file_props): - client = app.get_client(account) - session = client.get_module('Jingle').get_jingle_session( - jid=None, sid=file_props.sid) - file_transfers = self.instances['file_transfers'] hashes = Hashes2() try: file_ = open(file_props.file_name, 'rb') except Exception: return + log.debug('Computing file hash') hash_ = hashes.calculateHash(file_props.algo, file_) file_.close() - # If the hash we received and the hash of the file are the same, - # then the file is not corrupt - jid = file_props.sender + # File is corrupt if the calculated hash differs from the received hash if file_props.hash_ == hash_: - GLib.idle_add(self._popup_ft_result, account, jid, file_props) - GLib.idle_add(file_transfers.set_status, file_props, 'ok') + app.nec.push_incoming_event( + NetworkEvent('file-completed', + file_props=file_props)) else: # Wrong hash, we need to get the file again! file_props.error = -10 - GLib.idle_add(self._popup_ft_result, account, jid, file_props) - GLib.idle_add(file_transfers.set_status, file_props, 'hash_error') + jid = JID.from_string(file_props.sender) + app.nec.push_incoming_event( + NetworkEvent('file-hash-error', + file_props=file_props, + jid=jid.bare, + account=account)) # End jingle session + client = app.get_client(account) + session = client.get_module('Jingle').get_jingle_session( + jid=None, sid=file_props.sid) if session: session.end_session()
- def _popup_ft_result(self, account, jid, file_props): - file_transfers = self.instances['file_transfers'] - if helpers.allow_popup_window(account): - if file_props.error == 0: - if app.settings.get('notify_on_file_complete'): - file_transfers.show_completed(jid, file_props) - elif file_props.error == -1: - file_transfers.show_stopped( - jid, - file_props, - error_msg=_('Remote Contact Stopped Transfer')) - elif file_props.error == -6: - file_transfers.show_stopped( - jid, - file_props, - error_msg=_('Error Opening File')) - elif file_props.error == -10: - file_transfers.show_hash_error( - jid, - file_props, - account) - elif file_props.error == -12: - file_transfers.show_stopped( - jid, - file_props, - error_msg=_('SSL Certificate Error')) - return - - msg_type = '' - event_type = '' - if (file_props.error == 0 and - app.settings.get('notify_on_file_complete')): - event_class = events.FileCompletedEvent - msg_type = 'file-completed' - event_type = _('File Transfer Completed') - elif file_props.error in (-1, -6): - event_class = events.FileStoppedEvent - msg_type = 'file-stopped' - event_type = _('File Transfer Stopped') - elif file_props.error == -10: - event_class = events.FileHashErrorEvent - msg_type = 'file-hash-error' - event_type = _('File Transfer Failed') - - if event_type == '': - # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) - # this should never happen but it does. see process_result() in - # socks5.py - # who calls this func (sth is really wrong unless this func is also - # registered as progress_cb - return - - if msg_type: - event = event_class(file_props) - self.add_event(account, jid, event) - - if file_props is not None: - client = app.get_client(account) - - if file_props.type_ == 'r': - jid = JID.from_string(file_props.sender) - contact = client.get_module('contacts').get_contact(jid.bare) - filename = os.path.basename(file_props.file_name) - - if event_type == _('File Transfer Completed'): - txt = _('%(filename)s received from %(name)s.') % { - 'filename': filename, - 'name': contact.name} - icon_name = 'emblem-default' - elif event_type == _('File Transfer Stopped'): - txt = _('File transfer of %(filename)s from %(name)s ' - 'stopped.') % { - 'filename': filename, - 'name': contact.name} - icon_name = 'process-stop' - else: # File transfer hash error - txt = _('File transfer of %(filename)s from %(name)s ' - 'failed.') % { - 'filename': filename, - 'name': contact.name} - icon_name = 'process-stop' - else: - receiver = file_props.receiver - if hasattr(receiver, 'jid'): - receiver = receiver.jid - - jid = JID.from_string(receiver) - contact = client.get_module('contacts').get_contact(jid.bare) - filename = os.path.basename(file_props.file_name) - if event_type == _('File Transfer Completed'): - txt = _('You successfully sent %(filename)s to ' - '%(name)s.') % { - 'filename': filename, - 'name': contact.name} - icon_name = 'emblem-default' - elif event_type == _('File Transfer Stopped'): - txt = _('File transfer of %(filename)s to %(name)s ' - 'stopped.') % { - 'filename': filename, - 'name': contact.name} - icon_name = 'process-stop' - else: # File transfer hash error - txt = _('File transfer of %(filename)s to %(name)s ' - 'failed.') % { - 'filename': filename, - 'name': contact.name} - icon_name = 'process-stop' - else: - txt = '' - icon_name = None - - if (app.settings.get('notify_on_file_complete') and - helpers.allow_showing_notification(account)): - # We want to be notified and we are online/chat or we don't mind - # to be bugged when away/na/busy - app.notification.popup( - event_type, - jid, - account, - msg_type, - icon_name=icon_name, - title=event_type, - text=txt) - def send_httpupload(self, chat_control, path=None): if path is not None: self._send_httpupload(chat_control, path)
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/028f5e50938413d8858d2fe674378fd3...