diff --git a/gajim/app_actions.py b/gajim/app_actions.py
index 759012507..8ca7a9884 100644
--- a/gajim/app_actions.py
+++ b/gajim/app_actions.py
@@ -44,6 +44,7 @@ from gajim.gtk.history import HistoryWindow
from gajim.gtk.accounts import AccountsWindow
from gajim.gtk.proxies import ManageProxies
from gajim.gtk.discovery import ServiceDiscoveryWindow
+from gajim.gtk.blocking import BlockingList
# General Actions
@@ -211,6 +212,15 @@ def on_mam_preferences(action, param):
window.present()
+def on_blocking_list(action, param):
+ account = param.get_string()
+ window = app.get_app_window(MamPreferences, account)
+ if window is None:
+ BlockingList(account)
+ else:
+ window.present()
+
+
def on_history_sync(action, param):
account = param.get_string()
if 'history_sync' in interface.instances[account]:
diff --git a/gajim/application.py b/gajim/application.py
index fb206529a..b2da1f9fd 100644
--- a/gajim/application.py
+++ b/gajim/application.py
@@ -469,6 +469,7 @@ class GajimApplication(Gtk.Application):
('-archive', a.on_mam_preferences, 'feature', 's'),
('-sync-history', a.on_history_sync, 'online', 's'),
('-privacylists', a.on_privacy_lists, 'feature', 's'),
+ ('-blocking', a.on_blocking_list, 'feature', 's'),
('-send-server-message', a.on_send_server_message, 'online', 's'),
('-set-motd', a.on_set_motd, 'online', 's'),
('-update-motd', a.on_update_motd, 'online', 's'),
@@ -517,3 +518,6 @@ class GajimApplication(Gtk.Application):
elif event.feature == nbxmpp.NS_PRIVACY:
action = '%s-privacylists' % event.account
self.lookup_action(action).set_enabled(True)
+ elif event.feature == nbxmpp.NS_BLOCKING:
+ action = '%s-blocking' % event.account
+ self.lookup_action(action).set_enabled(True)
diff --git a/gajim/common/modules/blocking.py b/gajim/common/modules/blocking.py
index 1b208d29a..d0947e0a9 100644
--- a/gajim/common/modules/blocking.py
+++ b/gajim/common/modules/blocking.py
@@ -15,15 +15,26 @@
# XEP-0191: Blocking Command
import logging
+from functools import wraps
import nbxmpp
from gajim.common import app
+from gajim.common.nec import NetworkEvent
from gajim.common.nec import NetworkIncomingEvent
log = logging.getLogger('gajim.c.m.blocking')
+def ensure_online(func):
+ @wraps(func)
+ def func_wrapper(self, *args, **kwargs):
+ if not app.account_is_connected(self._account):
+ return
+ return func(self, *args, **kwargs)
+ return func_wrapper
+
+
class Blocking:
def __init__(self, con):
self._con = con
@@ -37,37 +48,48 @@ class Blocking:
self.supported = False
+ self._nbmxpp_methods = [
+ 'block',
+ 'unblock',
+ ]
+
+ def __getattr__(self, key):
+ if key not in self._nbmxpp_methods:
+ raise AttributeError
+ if not app.account_is_connected(self._account):
+ log.warning('Account %s not connected, cant use %s',
+ self._account, key)
+ return
+ module = self._con.connection.get_module('Blocking')
+ return getattr(module, key)
+
def pass_disco(self, from_, _identities, features, _data, _node):
if nbxmpp.NS_BLOCKING not in features:
return
self.supported = True
+ app.nec.push_incoming_event(
+ NetworkEvent('feature-discovered',
+ account=self._account,
+ feature=nbxmpp.NS_BLOCKING))
+
log.info('Discovered blocking: %s', from_)
- def get_blocking_list(self) -> None:
- if not self.supported:
- return
- iq = nbxmpp.Iq('get', nbxmpp.NS_BLOCKING)
- iq.setQuery('blocklist')
+ @ensure_online
+ def get_blocking_list(self, callback=None):
log.info('Request list')
- self._con.connection.SendAndCallForResponse(
- iq, self._blocking_list_received)
+ if callback is None:
+ callback = self._blocking_list_received
- def _blocking_list_received(self, stanza: nbxmpp.Iq) -> None:
- if not nbxmpp.isResultNode(stanza):
- log.info('Error: %s', stanza.getError())
+ self._con.connection.get_module('Blocking').get_blocking_list(
+ callback=callback)
+
+ def _blocking_list_received(self, result):
+ if result.is_error:
+ log.info('Error: %s', result.error)
return
- self.blocked = []
- blocklist = stanza.getTag('blocklist', namespace=nbxmpp.NS_BLOCKING)
- if blocklist is None:
- log.error('No blocklist node')
- return
-
- for item in blocklist.getTags('item'):
- self.blocked.append(item.getAttr('jid'))
- log.info('Received list: %s', self.blocked)
-
+ self.blocked = result.blocking_list
app.nec.push_incoming_event(
BlockingEvent(None, conn=self._con, changed=self.blocked))
@@ -129,35 +151,6 @@ class Blocking:
probe = nbxmpp.Presence(jid, 'probe', frm=self._con.get_own_jid())
self._con.connection.send(probe)
- def block(self, contact_list):
- if not self.supported:
- return
- iq = nbxmpp.Iq('set', nbxmpp.NS_BLOCKING)
- query = iq.setQuery(name='block')
-
- for contact in contact_list:
- query.addChild(name='item', attrs={'jid': contact.jid})
- log.info('Block: %s', contact.jid)
- self._con.connection.SendAndCallForResponse(
- iq, self._default_result_handler, {})
-
- def unblock(self, contact_list):
- if not self.supported:
- return
- iq = nbxmpp.Iq('set', nbxmpp.NS_BLOCKING)
- query = iq.setQuery(name='unblock')
-
- for contact in contact_list:
- query.addChild(name='item', attrs={'jid': contact.jid})
- log.info('Unblock: %s', contact.jid)
- self._con.connection.SendAndCallForResponse(
- iq, self._default_result_handler, {})
-
- @staticmethod
- def _default_result_handler(_con, stanza):
- if not nbxmpp.isResultNode(stanza):
- log.warning('Operation failed: %s', stanza.getError())
-
class BlockingEvent(NetworkIncomingEvent):
name = 'blocking'
diff --git a/gajim/common/modules/privacylists.py b/gajim/common/modules/privacylists.py
index 0837ea407..c69746733 100644
--- a/gajim/common/modules/privacylists.py
+++ b/gajim/common/modules/privacylists.py
@@ -304,7 +304,8 @@ class PrivacyLists:
def block_contacts(self, contact_list, message):
if not self.supported:
- self._con.get_module('Blocking').block(contact_list)
+ jid_list = [contact.jid for contact in contact_list]
+ self._con.get_module('Blocking').block(jid_list)
return
if self.default_list is None:
@@ -350,7 +351,8 @@ class PrivacyLists:
def unblock_contacts(self, contact_list):
if not self.supported:
- self._con.get_module('Blocking').unblock(contact_list)
+ jid_list = [contact.jid for contact in contact_list]
+ self._con.get_module('Blocking').unblock(jid_list)
return
new_blocked_list = []
diff --git a/gajim/data/gui/blocking_list.ui b/gajim/data/gui/blocking_list.ui
new file mode 100644
index 000000000..ff3310868
--- /dev/null
+++ b/gajim/data/gui/blocking_list.ui
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
diff --git a/gajim/gtk/blocking.py b/gajim/gtk/blocking.py
new file mode 100644
index 000000000..8b5d53322
--- /dev/null
+++ b/gajim/gtk/blocking.py
@@ -0,0 +1,150 @@
+# 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 .
+
+import logging
+
+from gi.repository import Gtk
+from gi.repository import Gdk
+
+from gajim.common import app
+from gajim.common.i18n import _
+
+from gajim.gtk.util import get_builder
+from gajim.gtk.dialogs import HigDialog
+
+log = logging.getLogger('gajim.gtk.blocking_list')
+
+
+class BlockingList(Gtk.ApplicationWindow):
+ def __init__(self, account):
+ Gtk.ApplicationWindow.__init__(self)
+ self.set_application(app.app)
+ self.set_position(Gtk.WindowPosition.CENTER)
+ self.set_show_menubar(False)
+ self.set_title(_('Blocking List for %s') % account)
+
+ self.connect('key-press-event', self._on_key_press)
+
+ self.account = account
+ self._con = app.connections[account]
+ self._prev_blocked_jids = set()
+ self._await_results = 2
+ self._received_errors = False
+
+ self._ui = get_builder('blocking_list.ui')
+ self.add(self._ui.blocking_grid)
+
+ self._spinner = Gtk.Spinner()
+ self._ui.overlay.add_overlay(self._spinner)
+
+ self._set_grid_state(False)
+ self._ui.connect_signals(self)
+ self.show_all()
+
+ self._activate_spinner()
+
+ self._con.get_module('Blocking').get_blocking_list(
+ callback=self._on_blocking_list_received)
+
+ def _reset_after_error(self):
+ self._received_errors = False
+ self._await_results = 2
+ self._disable_spinner()
+ self._set_grid_state(True)
+
+ def _show_error(self, error):
+ dialog = HigDialog(
+ self, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
+ _('Error!'),
+ error)
+ dialog.popup()
+
+ def _on_blocking_list_received(self, result):
+ self._disable_spinner()
+ self._set_grid_state(not result.is_error)
+
+ if result.is_error:
+ self._show_error(result.error)
+
+ else:
+ self._prev_blocked_jids = set(result.blocking_list)
+ self._ui.blocking_store.clear()
+ for item in result.blocking_list:
+ self._ui.blocking_store.append((item,))
+
+ def _on_save_result(self, result):
+ self._await_results -= 1
+ if result.is_error and not self._received_errors:
+ self._show_error(result.error)
+ self._received_errors = True
+
+ if not self._await_results:
+ if self._received_errors:
+ self._reset_after_error()
+ else:
+ self.destroy()
+
+ def _set_grid_state(self, state):
+ self._ui.blocking_grid.set_sensitive(state)
+
+ def _jid_edited(self, _renderer, path, new_text):
+ iter_ = self._ui.blocking_store.get_iter(path)
+ self._ui.blocking_store.set_value(iter_, 0, new_text)
+
+ def _on_add(self, _button):
+ self._ui.blocking_store.append([''])
+
+ def _on_remove(self, _button):
+ mod, paths = self._ui.block_view.get_selection().get_selected_rows()
+ for path in paths:
+ iter_ = mod.get_iter(path)
+ self._ui.blocking_store.remove(iter_)
+
+ def _on_save(self, _button):
+ self._activate_spinner()
+ self._set_grid_state(False)
+
+ blocked_jids = set()
+ for item in self._ui.blocking_store:
+ blocked_jids.add(item[0].lower())
+
+ unblock_jids = self._prev_blocked_jids - blocked_jids
+ if unblock_jids:
+ self._con.get_module('Blocking').unblock(
+ unblock_jids, callback=self._on_save_result)
+ else:
+ self._await_results -= 1
+
+ block_jids = blocked_jids - self._prev_blocked_jids
+ if block_jids:
+ self._con.get_module('Blocking').block(
+ block_jids, callback=self._on_save_result)
+ else:
+ self._await_results -= 1
+
+ if not self._await_results:
+ # No changes
+ self.destroy()
+
+ def _activate_spinner(self):
+ self._spinner.show()
+ self._spinner.start()
+
+ def _disable_spinner(self):
+ self._spinner.hide()
+ self._spinner.stop()
+
+ def _on_key_press(self, _widget, event):
+ if event.keyval == Gdk.KEY_Escape:
+ self.destroy()
diff --git a/gajim/gui_menu_builder.py b/gajim/gui_menu_builder.py
index 3cbc7f1d3..ef67df5ff 100644
--- a/gajim/gui_menu_builder.py
+++ b/gajim/gui_menu_builder.py
@@ -788,6 +788,7 @@ def get_account_menu(account):
('-start-single-chat', _('Send Single Messageā¦')),
(_('Advanced'), [
('-archive', _('Archiving Preferences')),
+ ('-blocking', _('Blocking List')),
('-sync-history', _('Synchronise History')),
('-privacylists', _('Privacy Lists')),
('-server-info', _('Server Info')),