From 2ca0ca38a59d5c944ff697d20cb373ce3945d2cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sun, 1 Jul 2018 02:16:33 +0200 Subject: [PATCH] Refactor Pubsub/Bookmarks/UserAvatar into own modules --- gajim/common/connection.py | 125 +-------- gajim/common/connection_handlers.py | 62 +---- gajim/common/connection_handlers_events.py | 112 -------- gajim/common/const.py | 5 + gajim/common/modules/bookmarks.py | 294 ++++++++++++++++++++ gajim/common/modules/pubsub.py | 217 +++++++++++++++ gajim/common/modules/user_avatar.py | 96 +++++++ gajim/common/pep.py | 18 +- gajim/common/pubsub.py | 306 --------------------- gajim/config.py | 38 ++- gajim/dialogs.py | 14 +- gajim/disco.py | 51 ++-- gajim/groupchat_control.py | 26 +- gajim/groups.py | 4 +- gajim/gui_interface.py | 65 +---- gajim/gui_menu_builder.py | 25 +- gajim/roster_window.py | 21 +- 17 files changed, 746 insertions(+), 733 deletions(-) create mode 100644 gajim/common/modules/bookmarks.py create mode 100644 gajim/common/modules/pubsub.py create mode 100644 gajim/common/modules/user_avatar.py delete mode 100644 gajim/common/pubsub.py diff --git a/gajim/common/connection.py b/gajim/common/connection.py index 4fc325604..e637006c2 100644 --- a/gajim/common/connection.py +++ b/gajim/common/connection.py @@ -73,6 +73,9 @@ from gajim.common.modules.last_activity import LastActivity from gajim.common.modules.http_auth import HTTPAuth from gajim.common.modules.vcard_temp import VCardTemp from gajim.common.modules.vcard_avatars import VCardAvatars +from gajim.common.modules.pubsub import PubSub +from gajim.common.modules.bookmarks import Bookmarks +from gajim.common.modules.user_avatar import UserAvatar from gajim.common.connection_handlers import * from gajim.common.contacts import GC_Contact from gajim.gtkgui_helpers import get_action @@ -113,7 +116,6 @@ class CommonConnection: self.old_show = '' self.priority = app.get_priority(name, 'offline') self.time_to_reconnect = None - self.bookmarks = [] self.blocked_list = [] self.blocked_contacts = [] @@ -491,18 +493,6 @@ class CommonConnection: def account_changed(self, new_name): self.name = new_name - def get_bookmarks(self): - """ - To be implemented by derived classes - """ - raise NotImplementedError - - def store_bookmarks(self): - """ - To be implemented by derived classes - """ - raise NotImplementedError - def get_metacontacts(self): """ To be implemented by derived classes @@ -670,6 +660,9 @@ class Connection(CommonConnection, ConnectionHandlers): self.register_module('HTTPAuth', HTTPAuth, self) self.register_module('VCardTemp', VCardTemp, self) self.register_module('VCardAvatars', VCardAvatars, self) + self.register_module('PubSub', PubSub, self) + self.register_module('Bookmarks', Bookmarks, self) + self.register_module('UserAvatar', UserAvatar, self) app.ged.register_event_handler('privacy-list-received', ged.CORE, self._nec_privacy_list_received) @@ -1774,8 +1767,8 @@ class Connection(CommonConnection, ConnectionHandlers): # ask our VCard self.get_module('VCardTemp').request_vcard() - # Get bookmarks from private namespace - self.get_bookmarks() + # Get bookmarks + self.get_module('Bookmarks').get_bookmarks() # Get annotations self.get_module('Annotations').get_annotations() @@ -1916,8 +1909,7 @@ class Connection(CommonConnection, ConnectionHandlers): self.pubsub_publish_options_supported = True else: # Remove stored bookmarks accessible to everyone. - self.send_pb_purge(our_jid, 'storage:bookmarks') - self.send_pb_delete(our_jid, 'storage:bookmarks') + self.get_module('Bookmarks').purge_pubsub_bookmarks() if obj.fjid == hostname: if nbxmpp.NS_SECLABEL in obj.features: @@ -2255,99 +2247,6 @@ class Connection(CommonConnection, ConnectionHandlers): return True return False - def _request_bookmarks_xml(self): - if not app.account_is_connected(self.name): - return - iq = nbxmpp.Iq(typ='get') - iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE) - iq2.addChild(name='storage', namespace='storage:bookmarks') - self.connection.send(iq) - app.log('bookmarks').info('Request Bookmarks (PrivateStorage)') - - def _check_bookmarks_received(self): - if not self.bookmarks: - self._request_bookmarks_xml() - - def get_bookmarks(self, storage_type=None): - """ - Get Bookmarks from storage or PubSub if supported as described in XEP - 0048 - - storage_type can be set to xml to force request to xml storage - """ - if not app.account_is_connected(self.name): - return - - if storage_type != 'xml': - if self.pep_supported and self.pubsub_publish_options_supported: - self.send_pb_retrieve('', 'storage:bookmarks') - app.log('bookmarks').info('Request Bookmarks (PubSub)') - # some server (ejabberd) are so slow to answer that we - # request via XML if we don't get answer in the next 30 seconds - app.idlequeue.set_alarm(self._check_bookmarks_received, 30) - return - - self._request_bookmarks_xml() - - def get_bookmarks_storage_node(self): - NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks' - storage_node = nbxmpp.Node( - tag='storage', attrs={'xmlns': 'storage:bookmarks'}) - for bm in self.bookmarks: - conf_node = storage_node.addChild(name="conference") - conf_node.setAttr('jid', bm['jid']) - conf_node.setAttr('autojoin', bm['autojoin']) - conf_node.setAttr('name', bm['name']) - conf_node.setTag( - 'minimize', namespace=NS_GAJIM_BM).setData(bm['minimize']) - # Only add optional elements if not empty - # Note: need to handle both None and '' as empty - # thus shouldn't use "is not None" - if bm.get('nick', None): - conf_node.setTagData('nick', bm['nick']) - if bm.get('password', None): - conf_node.setTagData('password', bm['password']) - if bm.get('print_status', None): - conf_node.setTag( - 'print_status', - namespace=NS_GAJIM_BM).setData(bm['print_status']) - return storage_node - - @staticmethod - def get_bookmark_publish_options(): - options = nbxmpp.Node(nbxmpp.NS_DATA + ' x', - attrs={'type': 'submit'}) - f = options.addChild('field', - attrs={'var': 'FORM_TYPE', 'type': 'hidden'}) - f.setTagData('value', nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS) - f = options.addChild('field', attrs={'var': 'pubsub#access_model'}) - f.setTagData('value', 'whitelist') - return options - - def store_bookmarks(self, storage_type=None): - """ - Send bookmarks to the storage namespace or PubSub if supported - - storage_type can be set to 'pubsub' or 'xml' so store in only one method - else it will be stored on both - """ - if not app.account_is_connected(self.name): - return - - storage_node = self.get_bookmarks_storage_node() - - if storage_type != 'xml': - if self.pep_supported and self.pubsub_publish_options_supported: - self.send_pb_publish( - '', 'storage:bookmarks', storage_node, 'current', - options=self.get_bookmark_publish_options()) - app.log('bookmarks').info('Bookmarks published (PubSub)') - - if storage_type != 'pubsub': - iq = nbxmpp.Iq('set', nbxmpp.NS_PRIVATE, payload=storage_node) - self.connection.send(iq) - app.log('bookmarks').info('Bookmarks published (PrivateStorage)') - def get_roster_delimiter(self): """ Get roster group delimiter from storage as described in XEP 0083 @@ -2614,11 +2513,7 @@ class Connection(CommonConnection, ConnectionHandlers): destroy.setAttr('jid', jid) self.connection.send(iq) i = 0 - for bm in self.bookmarks: - if bm['jid'] == room_jid: - del self.bookmarks[i] - break - i += 1 + self.get_module('Bookmarks').bookmarks.pop(jid, None) self.store_bookmarks() def send_gc_status(self, nick, jid, show, status, auto=False): diff --git a/gajim/common/connection_handlers.py b/gajim/common/connection_handlers.py index be25c7828..3f79a2961 100644 --- a/gajim/common/connection_handlers.py +++ b/gajim/common/connection_handlers.py @@ -50,7 +50,6 @@ from gajim.common import jingle_xtls from gajim.common import configpaths from gajim.common.caps_cache import muc_caps_cache from gajim.common.commands import ConnectionCommands -from gajim.common.pubsub import ConnectionPubSub from gajim.common.protocol.caps import ConnectionCaps from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream from gajim.common.protocol.bytestream import ConnectionIBBytestream @@ -339,15 +338,15 @@ class ConnectionPEP(object): if message: i = item.addChild('text') i.addData(message) - self._pubsub_connection.send_pb_publish('', nbxmpp.NS_ACTIVITY, item, - '0') + self.get_module('PubSub').send_pb_publish( + '', nbxmpp.NS_ACTIVITY, item, '0') def retract_activity(self): if not self.pep_supported: return self.send_activity(None) # not all client support new XEP, so we still retract - self._pubsub_connection.send_pb_retract('', nbxmpp.NS_ACTIVITY, '0') + self.get_module('PubSub').send_pb_retract('', nbxmpp.NS_ACTIVITY, '0') def send_mood(self, mood, message=None): if self.connected == 1: @@ -363,14 +362,14 @@ class ConnectionPEP(object): if message: i = item.addChild('text') i.addData(message) - self._pubsub_connection.send_pb_publish('', nbxmpp.NS_MOOD, item, '0') + self.get_module('PubSub').send_pb_publish('', nbxmpp.NS_MOOD, item, '0') def retract_mood(self): if not self.pep_supported: return self.send_mood(None) # not all client support new XEP, so we still retract - self._pubsub_connection.send_pb_retract('', nbxmpp.NS_MOOD, '0') + self.get_module('PubSub').send_pb_retract('', nbxmpp.NS_MOOD, '0') def send_tune(self, artist='', title='', source='', track=0, length=0, items=None): @@ -399,14 +398,14 @@ class ConnectionPEP(object): i.addData(length) if items: item.addChild(payload=items) - self._pubsub_connection.send_pb_publish('', nbxmpp.NS_TUNE, item, '0') + self.get_module('PubSub').send_pb_publish('', nbxmpp.NS_TUNE, item, '0') def retract_tune(self): if not self.pep_supported: return self.send_tune(None) # not all client support new XEP, so we still retract - self._pubsub_connection.send_pb_retract('', nbxmpp.NS_TUNE, '0') + self.get_module('PubSub').send_pb_retract('', nbxmpp.NS_TUNE, '0') def send_nickname(self, nick): if self.connected == 1: @@ -418,13 +417,13 @@ class ConnectionPEP(object): return item = nbxmpp.Node('nick', {'xmlns': nbxmpp.NS_NICK}) item.addData(nick) - self._pubsub_connection.send_pb_publish('', nbxmpp.NS_NICK, item, '0') + self.get_module('PubSub').send_pb_publish('', nbxmpp.NS_NICK, item, '0') def retract_nickname(self): if not self.pep_supported: return - self._pubsub_connection.send_pb_retract('', nbxmpp.NS_NICK, '0') + self.get_module('PubSub').send_pb_retract('', nbxmpp.NS_NICK, '0') def send_location(self, info): if self.connected == 1: @@ -439,14 +438,14 @@ class ConnectionPEP(object): if info.get(field, None): i = item.addChild(field) i.addData(info[field]) - self._pubsub_connection.send_pb_publish('', nbxmpp.NS_LOCATION, item, '0') + self.get_module('PubSub').send_pb_publish('', nbxmpp.NS_LOCATION, item, '0') def retract_location(self): if not self.pep_supported: return self.send_location({}) # not all client support new XEP, so we still retract - self._pubsub_connection.send_pb_retract('', nbxmpp.NS_LOCATION, '0') + self.get_module('PubSub').send_pb_retract('', nbxmpp.NS_LOCATION, '0') # basic connection handlers used here and in zeroconf class ConnectionHandlersBase: @@ -915,7 +914,7 @@ class ConnectionHandlersBase: class ConnectionHandlers(ConnectionArchive313, ConnectionSocks5Bytestream, ConnectionDisco, -ConnectionCommands, ConnectionPubSub, ConnectionPEP, ConnectionCaps, +ConnectionCommands, ConnectionPEP, ConnectionCaps, ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream, ConnectionHTTPUpload): def __init__(self): @@ -923,7 +922,6 @@ ConnectionHTTPUpload): ConnectionSocks5Bytestream.__init__(self) ConnectionIBBytestream.__init__(self) ConnectionCommands.__init__(self) - ConnectionPubSub.__init__(self) ConnectionPEP.__init__(self, account=self.name, dispatcher=self, pubsub_connection=self) ConnectionHTTPUpload.__init__(self) @@ -949,8 +947,6 @@ ConnectionHTTPUpload): self.privacy_default_list = None - app.nec.register_incoming_event(PrivateStorageBookmarksReceivedEvent) - app.nec.register_incoming_event(BookmarksReceivedEvent) app.nec.register_incoming_event(StreamConflictReceivedEvent) app.nec.register_incoming_event(StreamOtherHostReceivedEvent) app.nec.register_incoming_event(MessageReceivedEvent) @@ -961,8 +957,6 @@ ConnectionHTTPUpload): app.ged.register_event_handler('roster-set-received', ged.CORE, self._nec_roster_set_received) - app.ged.register_event_handler('private-storage-bookmarks-received', - ged.CORE, self._nec_private_storate_bookmarks_received) app.ged.register_event_handler('roster-received', ged.CORE, self._nec_roster_received) app.ged.register_event_handler('iq-error-received', ged.CORE, @@ -988,12 +982,9 @@ ConnectionHTTPUpload): ConnectionHandlersBase.cleanup(self) ConnectionCaps.cleanup(self) ConnectionArchive313.cleanup(self) - ConnectionPubSub.cleanup(self) ConnectionHTTPUpload.cleanup(self) app.ged.remove_event_handler('roster-set-received', ged.CORE, self._nec_roster_set_received) - app.ged.remove_event_handler('private-storage-bookmarks-received', - ged.CORE, self._nec_private_storate_bookmarks_received) app.ged.remove_event_handler('roster-received', ged.CORE, self._nec_roster_received) app.ged.remove_event_handler('iq-error-received', ged.CORE, @@ -1144,28 +1135,6 @@ ConnectionHTTPUpload): conn=self, stanza=obj.stanza)) return True - def _nec_private_storate_bookmarks_received(self, obj): - if obj.conn.name != self.name: - return - app.log('bookmarks').info('Received Bookmarks (PrivateStorage)') - resend_to_pubsub = False - bm_jids = [b['jid'] for b in self.bookmarks] - for bm in obj.bookmarks: - if bm['jid'] not in bm_jids: - self.bookmarks.append(bm) - # We got a bookmark that was not in pubsub - resend_to_pubsub = True - if resend_to_pubsub: - self.store_bookmarks('pubsub') - - def _PrivateCB(self, con, iq_obj): - """ - Private Data (XEP 048 and 049) - """ - log.debug('PrivateCB') - app.nec.push_incoming_event(PrivateStorageReceivedEvent(None, - conn=self, stanza=iq_obj)) - def _SecLabelCB(self, con, iq_obj): """ Security Label callback, used for catalogues. @@ -1540,8 +1509,8 @@ ConnectionHTTPUpload): # ask our VCard self.get_module('VCardTemp').request_vcard() - # Get bookmarks from private namespace - self.get_bookmarks() + # Get bookmarks + self.get_module('Bookmarks').get_bookmarks() # Get annotations from private namespace self.get_module('Annotations').get_annotations() @@ -1645,7 +1614,6 @@ ConnectionHTTPUpload): nbxmpp.NS_MUC_OWNER) con.RegisterHandler('iq', self._MucAdminCB, 'result', nbxmpp.NS_MUC_ADMIN) - con.RegisterHandler('iq', self._PrivateCB, 'result', nbxmpp.NS_PRIVATE) con.RegisterHandler('iq', self._SecLabelCB, 'result', nbxmpp.NS_SECLABEL_CATALOG) con.RegisterHandler('iq', self._CommandExecuteCB, 'set', @@ -1657,8 +1625,6 @@ ConnectionHTTPUpload): con.RegisterHandler('iq', self._PrivacySetCB, 'set', nbxmpp.NS_PRIVACY) con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_1) con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_2) - con.RegisterHandler('iq', self._PubSubCB, 'result') - con.RegisterHandler('iq', self._PubSubErrorCB, 'error') con.RegisterHandler('iq', self._JingleCB, 'result') con.RegisterHandler('iq', self._JingleCB, 'error') con.RegisterHandler('iq', self._JingleCB, 'set', nbxmpp.NS_JINGLE) diff --git a/gajim/common/connection_handlers_events.py b/gajim/common/connection_handlers_events.py index 4027a22bd..52fce3931 100644 --- a/gajim/common/connection_handlers_events.py +++ b/gajim/common/connection_handlers_events.py @@ -309,118 +309,6 @@ class MucAdminReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.users_dict[jid]['reason'] = reason return True -class PrivateStorageReceivedEvent(nec.NetworkIncomingEvent): - name = 'private-storage-received' - base_network_events = [] - - def generate(self): - query = self.stanza.getTag('query') - self.storage_node = query.getTag('storage') - if self.storage_node: - self.namespace = self.storage_node.getNamespace() - return True - - -class BookmarksHelper: - def parse_bookmarks(self): - self.bookmarks = [] - NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks' - confs = self.storage_node.getTags('conference') - for conf in confs: - autojoin_val = conf.getAttr('autojoin') - if not autojoin_val: # not there (it's optional) - autojoin_val = False - minimize_val = conf.getTag('minimize', namespace=NS_GAJIM_BM) - if not minimize_val: # not there, try old Gajim behaviour - minimize_val = conf.getAttr('minimize') - if not minimize_val: # not there (it's optional) - minimize_val = False - else: - minimize_val = minimize_val.getData() - - print_status = conf.getTag('print_status', namespace=NS_GAJIM_BM) - if not print_status: # not there, try old Gajim behaviour - print_status = conf.getTagData('print_status') - if not print_status: # not there, try old Gajim behaviour - print_status = conf.getTagData('show_status') - else: - print_status = print_status.getData() - - try: - jid = helpers.parse_jid(conf.getAttr('jid')) - except helpers.InvalidFormat: - log.warning('Invalid JID: %s, ignoring it' - % conf.getAttr('jid')) - continue - - bm = {'name': conf.getAttr('name'), - 'jid': jid, - 'autojoin': autojoin_val, - 'minimize': minimize_val, - 'password': conf.getTagData('password'), - 'nick': conf.getTagData('nick'), - 'print_status': print_status} - - bm_jids = [b['jid'] for b in self.bookmarks] - if bm['jid'] not in bm_jids: - self.bookmarks.append(bm) - -class PrivateStorageBookmarksReceivedEvent(nec.NetworkIncomingEvent, -BookmarksHelper): - name = 'private-storage-bookmarks-received' - base_network_events = ['private-storage-received'] - - def generate(self): - self.conn = self.base_event.conn - self.storage_node = self.base_event.storage_node - if self.base_event.namespace != nbxmpp.NS_BOOKMARKS: - return - self.parse_bookmarks() - return True - -class BookmarksReceivedEvent(nec.NetworkIncomingEvent): - name = 'bookmarks-received' - base_network_events = ['private-storage-bookmarks-received', - 'pubsub-bookmarks-received'] - - def generate(self): - self.conn = self.base_event.conn - self.bookmarks = self.base_event.bookmarks - return True - -class PubsubReceivedEvent(nec.NetworkIncomingEvent): - name = 'pubsub-received' - base_network_events = [] - - def generate(self): - self.jid = self.stanza.getFrom() - self.pubsub_node = self.stanza.getTag('pubsub') - if not self.pubsub_node: - return - self.items_node = self.pubsub_node.getTag('items') - if not self.items_node: - return - return True - -class PubsubBookmarksReceivedEvent(nec.NetworkIncomingEvent, BookmarksHelper): - name = 'pubsub-bookmarks-received' - base_network_events = ['pubsub-received'] - - def generate(self): - self.conn = self.base_event.conn - self.item_node = self.base_event.items_node.getTag('item') - if not self.item_node: - return - children = self.item_node.getChildren() - if not children: - return - self.storage_node = children[0] - ns = self.storage_node.getNamespace() - if ns != nbxmpp.NS_BOOKMARKS: - return - self.parse_bookmarks() - return True - class IqErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'iq-error-received' base_network_events = [] diff --git a/gajim/common/const.py b/gajim/common/const.py index c77cbb37b..66d675bd6 100644 --- a/gajim/common/const.py +++ b/gajim/common/const.py @@ -119,6 +119,11 @@ class RequestAvatar(IntEnum): ROOM = 1 USER = 2 +@unique +class BookmarkStorageType(IntEnum): + PRIVATE = 0 + PUBSUB = 1 + SSLError = { 2: _("Unable to get issuer certificate"), 3: _("Unable to get certificate CRL"), diff --git a/gajim/common/modules/bookmarks.py b/gajim/common/modules/bookmarks.py new file mode 100644 index 000000000..7aa5b23a0 --- /dev/null +++ b/gajim/common/modules/bookmarks.py @@ -0,0 +1,294 @@ +# 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 . + +# XEP-0048: Bookmarks + +import logging + +import nbxmpp + +from gajim.common import app +from gajim.common import helpers +from gajim.common.const import BookmarkStorageType +from gajim.common.nec import NetworkIncomingEvent + +log = logging.getLogger('gajim.c.m.bookmarks') + + +class Bookmarks: + def __init__(self, con): + self._con = con + self._account = con.name + self.bookmarks = {} + + self.handlers = [] + + def _pubsub_support(self): + return (self._con.pep_supported and + self._con.pubsub_publish_options_supported) + + def get_bookmarks(self, storage_type=None): + if not app.account_is_connected(self._account): + return + + if storage_type in (None, BookmarkStorageType.PUBSUB): + if self._pubsub_support(): + self._request_pubsub_bookmarks() + else: + # Fallback, request private storage + self._request_private_bookmarks() + else: + log.info('Request Bookmarks (PrivateStorage)') + self._request_private_bookmarks() + + def _request_pubsub_bookmarks(self): + log.info('Request Bookmarks (PubSub)') + self._con.get_module('PubSub').send_pb_retrieve( + '', 'storage:bookmarks', + cb=self._pubsub_bookmarks_received) + + def _pubsub_bookmarks_received(self, conn, stanza): + if not nbxmpp.isResultNode(stanza): + log.info('No pubsub bookmarks: %s', stanza.getError()) + # Fallback, request private storage + self._request_private_bookmarks() + return + + log.info('Received Bookmarks (PubSub)') + self._parse_bookmarks(stanza) + self._request_private_bookmarks() + + def _request_private_bookmarks(self): + if not app.account_is_connected(self._account): + return + + iq = nbxmpp.Iq(typ='get') + query = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE) + query.addChild(name='storage', namespace='storage:bookmarks') + log.info('Request Bookmarks (PrivateStorage)') + self._con.connection.SendAndCallForResponse( + iq, self._private_bookmarks_received) + + def _private_bookmarks_received(self, stanza): + if not nbxmpp.isResultNode(stanza): + log.info('No private bookmarks: %s', stanza.getError()) + else: + log.info('Received Bookmarks (PrivateStorage)') + merged = self._parse_bookmarks(stanza, check_merge=True) + if merged: + log.info('Merge PrivateStorage with PubSub') + self.store_bookmarks(BookmarkStorageType.PUBSUB) + self.auto_join_bookmarks() + app.nec.push_incoming_event(BookmarksReceivedEvent( + None, account=self._account)) + + @staticmethod + def _get_storage_node(stanza): + node = stanza.getTag('pubsub', namespace=nbxmpp.NS_PUBSUB) + if node is None: + node = stanza.getTag('event', namespace=nbxmpp.NS_PUBSUB_EVENT) + if node is None: + # Private Storage + query = stanza.getQuery() + if query is None: + return + storage = query.getTag('storage', + namespace=nbxmpp.NS_BOOKMARKS) + if storage is None: + return + return storage + + items_node = node.getTag('items') + if items_node is None: + return + if items_node.getAttr('node') != nbxmpp.NS_BOOKMARKS: + return + + item_node = items_node.getTag('item') + if item_node is None: + return + + storage = item_node.getTag('storage', namespace=nbxmpp.NS_BOOKMARKS) + if storage is None: + return + return storage + + def _parse_bookmarks(self, stanza, check_merge=False): + merged = False + storage = self._get_storage_node(stanza) + if storage is None: + return + + NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks' + confs = storage.getTags('conference') + for conf in confs: + autojoin_val = conf.getAttr('autojoin') + if not autojoin_val: # not there (it's optional) + autojoin_val = False + minimize_val = conf.getTag('minimize', namespace=NS_GAJIM_BM) + if not minimize_val: # not there, try old Gajim behaviour + minimize_val = conf.getAttr('minimize') + if not minimize_val: # not there (it's optional) + minimize_val = False + else: + minimize_val = minimize_val.getData() + + print_status = conf.getTag('print_status', namespace=NS_GAJIM_BM) + if not print_status: # not there, try old Gajim behaviour + print_status = conf.getTagData('print_status') + if not print_status: # not there, try old Gajim behaviour + print_status = conf.getTagData('show_status') + else: + print_status = print_status.getData() + + try: + jid = helpers.parse_jid(conf.getAttr('jid')) + except helpers.InvalidFormat: + log.warning('Invalid JID: %s, ignoring it' + % conf.getAttr('jid')) + continue + + if check_merge: + if jid not in self.bookmarks: + merged = True + + log.debug('Found Bookmark: %s', jid) + self.bookmarks[jid] = { + 'name': conf.getAttr('name'), + 'autojoin': autojoin_val, + 'minimize': minimize_val, + 'password': conf.getTagData('password'), + 'nick': conf.getTagData('nick'), + 'print_status': print_status} + + return merged + + def _build_storage_node(self): + NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks' + storage_node = nbxmpp.Node( + tag='storage', attrs={'xmlns': 'storage:bookmarks'}) + for jid, bm in self.bookmarks.items(): + conf_node = storage_node.addChild(name="conference") + conf_node.setAttr('jid', jid) + conf_node.setAttr('autojoin', bm['autojoin']) + conf_node.setAttr('name', bm['name']) + conf_node.setTag( + 'minimize', namespace=NS_GAJIM_BM).setData(bm['minimize']) + # Only add optional elements if not empty + # Note: need to handle both None and '' as empty + # thus shouldn't use "is not None" + if bm.get('nick', None): + conf_node.setTagData('nick', bm['nick']) + if bm.get('password', None): + conf_node.setTagData('password', bm['password']) + if bm.get('print_status', None): + conf_node.setTag( + 'print_status', + namespace=NS_GAJIM_BM).setData(bm['print_status']) + return storage_node + + @staticmethod + def get_bookmark_publish_options(): + options = nbxmpp.Node(nbxmpp.NS_DATA + ' x', + attrs={'type': 'submit'}) + f = options.addChild('field', + attrs={'var': 'FORM_TYPE', 'type': 'hidden'}) + f.setTagData('value', nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS) + f = options.addChild('field', attrs={'var': 'pubsub#access_model'}) + f.setTagData('value', 'whitelist') + return options + + def store_bookmarks(self, storage_type=None): + if not app.account_is_connected(self._account): + return + + storage_node = self._build_storage_node() + + if storage_type is None: + if self._pubsub_support(): + self._pubsub_store(storage_node) + else: + self._private_store(storage_node) + elif storage_type == BookmarkStorageType.PUBSUB: + if self._pubsub_support(): + self._pubsub_store(storage_node) + elif storage_type == BookmarkStorageType.PRIVATE: + self._private_store(storage_node) + + def _pubsub_store(self, storage_node): + self._con.get_module('PubSub').send_pb_publish( + '', 'storage:bookmarks', storage_node, 'current', + options=self.get_bookmark_publish_options(), + cb=self._pubsub_store_result) + log.info('Publish Bookmarks (PubSub)') + + def _private_store(self, storage_node): + iq = nbxmpp.Iq('set', nbxmpp.NS_PRIVATE, payload=storage_node) + log.info('Publish Bookmarks (PrivateStorage)') + self._con.connection.SendAndCallForResponse( + iq, self._private_store_result) + + def _pubsub_store_result(self, conn, stanza): + if not nbxmpp.isResultNode(stanza): + log.error('Error: %s', stanza.getError()) + return + + def _private_store_result(self, stanza): + if not nbxmpp.isResultNode(stanza): + log.error('Error: %s', stanza.getError()) + return + + def auto_join_bookmarks(self): + if app.is_invisible(self._account): + return + for jid, bm in self.bookmarks.items(): + if bm['autojoin'] in ('1', 'true'): + # Only join non-opened groupchats. Opened one are already + # auto-joined on re-connection + if jid not in app.gc_connected[self._account]: + # we are not already connected + minimize = bm['minimize'] in ('1', 'true') + app.interface.join_gc_room( + self._account, jid, bm['nick'], + bm['password'], minimize=minimize) + + def add_bookmark(self, name, jid, autojoin, + minimize, password, nick): + self.bookmarks[jid] = { + 'name': name, + 'autojoin': autojoin, + 'minimize': minimize, + 'password': password, + 'nick': nick} + + self.store_bookmarks() + app.nec.push_incoming_event(BookmarksReceivedEvent( + None, account=self._account)) + + def get_name_from_bookmark(self, jid): + try: + return self.bookmarks[jid]['name'] + except KeyError: + return jid.split('@')[0] + + def purge_pubsub_bookmarks(self): + log.info('Purge/Delete Bookmarks on PubSub, ' + 'because publish options are not available') + self._con.get_module('PubSub').send_pb_purge('', 'storage:bookmarks') + self._con.get_module('PubSub').send_pb_delete('', 'storage:bookmarks') + + +class BookmarksReceivedEvent(NetworkIncomingEvent): + name = 'bookmarks-received' + base_network_events = [] diff --git a/gajim/common/modules/pubsub.py b/gajim/common/modules/pubsub.py new file mode 100644 index 000000000..8626e3e5e --- /dev/null +++ b/gajim/common/modules/pubsub.py @@ -0,0 +1,217 @@ +# Copyright (C) 2006 Tomasz Melcer +# Copyright (C) 2006-2014 Yann Leboulanger +# Copyright (C) 2007 Jean-Marie Traissard +# Copyright (C) 2008 Stephan Erb +# Copyright (C) 2018 Philipp Hörist +# +# 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 . + +# XEP-0060: Publish-Subscribe + +import logging + +import nbxmpp + +from gajim.common import app + +log = logging.getLogger('gajim.c.m.pubsub') + + +class PubSub: + def __init__(self, con): + self._con = con + self._account = con.name + + self.handlers = [] + + def send_pb_subscription_query(self, jid, cb, **kwargs): + if not app.account_is_connected(self._account): + return + + query = nbxmpp.Iq('get', to=jid) + pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) + pb.addChild('subscriptions') + + self._con.connection.SendAndCallForResponse(query, cb, kwargs) + + def send_pb_subscribe(self, jid, node, cb, **kwargs): + if not app.account_is_connected(self._account): + return + + our_jid = app.get_jid_from_account(self._account) + query = nbxmpp.Iq('set', to=jid) + pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) + pb.addChild('subscribe', {'node': node, 'jid': our_jid}) + + self._con.connection.SendAndCallForResponse(query, cb, kwargs) + + def send_pb_unsubscribe(self, jid, node, cb, **kwargs): + if not app.account_is_connected(self._account): + return + + our_jid = app.get_jid_from_account(self._account) + query = nbxmpp.Iq('set', to=jid) + pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) + pb.addChild('unsubscribe', {'node': node, 'jid': our_jid}) + + self._con.connection.SendAndCallForResponse(query, cb, kwargs) + + def send_pb_publish(self, jid, node, item, + id_=None, options=None, cb=None, **kwargs): + if not app.account_is_connected(self._account): + return + + if cb is None: + cb = self._default_callback + + query = nbxmpp.Iq('set', to=jid) + e = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) + p = e.addChild('publish', {'node': node}) + attrs = {} + if id_: + attrs = {'id': id_} + p.addChild('item', attrs, [item]) + if options: + p = e.addChild('publish-options') + p.addChild(node=options) + + self._con.connection.SendAndCallForResponse(query, cb, kwargs) + + @staticmethod + def get_pb_retrieve_iq(jid, node, item_id=None): + """ + Get IQ to query items from a node + """ + query = nbxmpp.Iq('get', to=jid) + r = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) + r = r.addChild('items', {'node': node}) + if item_id is not None: + r.addChild('item', {'id': item_id}) + return query + + def send_pb_retrieve(self, jid, node, item_id=None, cb=None, **kwargs): + """ + Get items from a node + """ + if not app.account_is_connected(self._account): + return + + if cb is None: + cb = self._default_callback + + query = self.get_pb_retrieve_iq(jid, node, item_id) + + self._con.connection.SendAndCallForResponse(query, cb, kwargs) + + def send_pb_retract(self, jid, node, id_, cb=None, **kwargs): + """ + Delete item from a node + """ + if not app.account_is_connected(self._account): + return + + if cb is None: + cb = self._default_callback + + query = nbxmpp.Iq('set', to=jid) + r = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) + r = r.addChild('retract', {'node': node, 'notify': '1'}) + r = r.addChild('item', {'id': id_}) + + self._con.connection.SendAndCallForResponse(query, cb, kwargs) + + def send_pb_purge(self, jid, node, cb=None, **kwargs): + """ + Purge node: Remove all items + """ + if not app.account_is_connected(self._account): + return + + if cb is None: + cb = self._default_callback + + query = nbxmpp.Iq('set', to=jid) + d = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER) + d = d.addChild('purge', {'node': node}) + + self._con.connection.SendAndCallForResponse(query, cb, kwargs) + + def send_pb_delete(self, jid, node, on_ok=None, on_fail=None): + """ + Delete node + """ + if not app.account_is_connected(self._account): + return + query = nbxmpp.Iq('set', to=jid) + d = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER) + d = d.addChild('delete', {'node': node}) + + def response(con, resp, jid, node): + if resp.getType() == 'result' and on_ok: + on_ok(jid, node) + elif on_fail: + msg = resp.getErrorMsg() + on_fail(jid, node, msg) + + self._con.connection.SendAndCallForResponse( + query, response, {'jid': jid, 'node': node}) + + def send_pb_create(self, jid, node, cb, + configure=False, configure_form=None): + """ + Create a new node + """ + if not app.account_is_connected(self._account): + return + query = nbxmpp.Iq('set', to=jid) + c = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) + c = c.addChild('create', {'node': node}) + if configure: + conf = c.addChild('configure') + if configure_form is not None: + conf.addChild(node=configure_form) + + self._con.connection.SendAndCallForResponse(query, cb) + + def send_pb_configure(self, jid, node, form, cb=None): + if not app.account_is_connected(self._account): + return + + if cb is None: + cb = self._default_callback + + query = nbxmpp.Iq('set', to=jid) + c = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER) + c = c.addChild('configure', {'node': node}) + c.addChild(node=form) + + self._con.connection.SendAndCallForResponse(query, cb) + + def request_pb_configuration(self, jid, node, cb=None): + if not app.account_is_connected(self._account): + return + + if cb is None: + cb = self._default_callback + + query = nbxmpp.Iq('get', to=jid) + e = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER) + e = e.addChild('configure', {'node': node}) + + self._con.connection.SendAndCallForResponse(query, cb) + + def _default_callback(self, conn, stanza, *args, **kwargs): + if not nbxmpp.isResultNode(stanza): + log.warning('Error: %s', stanza.getError()) diff --git a/gajim/common/modules/user_avatar.py b/gajim/common/modules/user_avatar.py new file mode 100644 index 000000000..8a3f65b37 --- /dev/null +++ b/gajim/common/modules/user_avatar.py @@ -0,0 +1,96 @@ +# 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 . + +# XEP-0084: User Avatar + +import logging +import base64 +import binascii + +import nbxmpp + +from gajim.common import app +from gajim.common.exceptions import StanzaMalformed + +log = logging.getLogger('gajim.c.m.user_avatar') + + +class UserAvatar: + def __init__(self, con): + self._con = con + self._account = con.name + + self.handlers = [] + + def get_pubsub_avatar(self, jid, item_id): + log.info('Request: %s %s', jid, item_id) + self._con.get_module('PubSub').send_pb_retrieve( + jid, 'urn:xmpp:avatar:data', item_id, self._avatar_received) + + def _validate_avatar_node(self, stanza): + jid = stanza.getFrom() + if jid is None: + jid = self._con.get_own_jid().getStripped() + else: + jid = jid.getStripped() + + if nbxmpp.isErrorNode(stanza): + raise StanzaMalformed(stanza.getErrorMsg()) + + pubsub_node = stanza.getTag('pubsub') + if pubsub_node is None: + raise StanzaMalformed('No pubsub node', stanza) + + items_node = pubsub_node.getTag('items') + if items_node is None: + raise StanzaMalformed('No items node', stanza) + + if items_node.getAttr('node') != 'urn:xmpp:avatar:data': + raise StanzaMalformed('Wrong namespace', stanza) + + item = items_node.getTag('item') + if item is None: + raise StanzaMalformed('No item node', stanza) + + sha = item.getAttr('id') + data_tag = item.getTag('data', namespace='urn:xmpp:avatar:data') + if sha is None or data_tag is None: + raise StanzaMalformed('No id attr or data node found', stanza) + + data = data_tag.getData() + if data is None: + raise StanzaMalformed('Data node empty', stanza) + + data = base64.b64decode(data.encode('utf-8')) + + return jid, sha, data + + def _avatar_received(self, conn, stanza): + try: + jid, sha, data = self._validate_avatar_node(stanza) + except (StanzaMalformed, binascii.Error) as error: + log.warning('Error: %s %s', stanza.getFrom(), error) + return + + log.info('Received: %s %s', jid, sha) + app.interface.save_avatar(data) + + if self._con.get_own_jid().bareMatch(jid): + app.config.set_per('accounts', self._account, 'avatar_sha', sha) + else: + own_jid = self._con.get_own_jid().getStripped() + app.logger.set_avatar_sha(own_jid, jid, sha) + app.contacts.set_avatar(self._account, jid, sha) + + app.interface.update_avatar(self._account, jid) diff --git a/gajim/common/pep.py b/gajim/common/pep.py index 8c10b7da2..83b7ebb88 100644 --- a/gajim/common/pep.py +++ b/gajim/common/pep.py @@ -485,7 +485,8 @@ class AvatarNotificationPEP(AbstractPEP): for item in items.getTags('item'): metadata = item.getTag('metadata') if metadata is None: - app.log('avatar').warning('Invalid avatar stanza:\n%s', items) + app.log('c.m.user_avatar').warning( + 'Invalid avatar stanza:\n%s', items) break info = item.getTag('metadata').getTag('info') if info is not None: @@ -498,23 +499,22 @@ class AvatarNotificationPEP(AbstractPEP): con = app.connections[account] if self.avatar is None: # Remove avatar - app.log('avatar').info('Remove (Pubsub): %s', jid) + app.log('c.m.user_avatar').info('Remove: %s', jid) app.contacts.set_avatar(account, jid, None) own_jid = con.get_own_jid().getStripped() app.logger.set_avatar_sha(own_jid, jid, None) app.interface.update_avatar(account, jid) else: sha = app.contacts.get_avatar_sha(account, jid) - app.log('avatar').info( - 'Update (Pubsub): %s %s', jid, self.avatar['id']) + app.log('c.m.user_avatar').info( + 'Update: %s %s', jid, self.avatar['id']) if sha == self.avatar['id']: - app.log('avatar').info( - 'Avatar already known (Pubsub): %s %s', + app.log('c.m.user_avatar').info( + 'Avatar already known: %s %s', jid, self.avatar['id']) return - app.log('avatar').info('Request (Pubsub): %s', jid) - con.get_pubsub_avatar(jid, 'urn:xmpp:avatar:data', - self.avatar['id']) + con.get_module('UserAvatar').get_pubsub_avatar( + jid, self.avatar['id']) SUPPORTED_PERSONAL_USER_EVENTS = [ diff --git a/gajim/common/pubsub.py b/gajim/common/pubsub.py deleted file mode 100644 index 963c548a5..000000000 --- a/gajim/common/pubsub.py +++ /dev/null @@ -1,306 +0,0 @@ -# -*- coding:utf-8 -*- -## src/common/pubsub.py -## -## Copyright (C) 2006 Tomasz Melcer -## Copyright (C) 2006-2014 Yann Leboulanger -## Copyright (C) 2007 Jean-Marie Traissard -## Copyright (C) 2008 Stephan Erb -## -## 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 base64 -import binascii - -import nbxmpp -from gajim.common import app -#TODO: Doesn't work -#from common.connection_handlers import PEP_CONFIG -PEP_CONFIG = 'pep_config' -from gajim.common import ged -from gajim.common.connection_handlers_events import PubsubReceivedEvent -from gajim.common.connection_handlers_events import PubsubBookmarksReceivedEvent -from gajim.common.exceptions import StanzaMalformed - -import logging -log = logging.getLogger('gajim.c.pubsub') - -class ConnectionPubSub: - def __init__(self): - self.__callbacks = {} - app.nec.register_incoming_event(PubsubBookmarksReceivedEvent) - app.ged.register_event_handler('pubsub-bookmarks-received', - ged.CORE, self._nec_pubsub_bookmarks_received) - - def cleanup(self): - app.ged.remove_event_handler('pubsub-bookmarks-received', - ged.CORE, self._nec_pubsub_bookmarks_received) - - def send_pb_subscription_query(self, jid, cb, *args, **kwargs): - if not self.connection or self.connected < 2: - return - query = nbxmpp.Iq('get', to=jid) - pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) - pb.addChild('subscriptions') - - id_ = self.connection.send(query) - - self.__callbacks[id_] = (cb, args, kwargs) - - def send_pb_subscribe(self, jid, node, cb, *args, **kwargs): - if not self.connection or self.connected < 2: - return - our_jid = app.get_jid_from_account(self.name) - query = nbxmpp.Iq('set', to=jid) - pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) - pb.addChild('subscribe', {'node': node, 'jid': our_jid}) - - id_ = self.connection.send(query) - - self.__callbacks[id_] = (cb, args, kwargs) - - def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs): - if not self.connection or self.connected < 2: - return - our_jid = app.get_jid_from_account(self.name) - query = nbxmpp.Iq('set', to=jid) - pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) - pb.addChild('unsubscribe', {'node': node, 'jid': our_jid}) - - id_ = self.connection.send(query) - - self.__callbacks[id_] = (cb, args, kwargs) - - def send_pb_publish(self, jid, node, item, id_=None, options=None): - """ - Publish item to a node - """ - if not self.connection or self.connected < 2: - return - query = nbxmpp.Iq('set', to=jid) - e = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) - p = e.addChild('publish', {'node': node}) - attrs = {} - if id_: - attrs = {'id': id_} - p.addChild('item', attrs, [item]) - if options: - p = e.addChild('publish-options') - p.addChild(node=options) - - self.connection.send(query) - - @staticmethod - def get_pb_retrieve_iq(jid, node, item_id=None): - """ - Get IQ to query items from a node - """ - query = nbxmpp.Iq('get', to=jid) - r = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) - r = r.addChild('items', {'node': node}) - if item_id is not None: - r.addChild('item', {'id': item_id}) - return query - - def send_pb_retrieve(self, jid, node, item_id=None, cb=None, *args, **kwargs): - """ - Get items from a node - """ - if not self.connection or self.connected < 2: - return - query = self.get_pb_retrieve_iq(jid, node, item_id) - id_ = self.connection.send(query) - - if cb: - self.__callbacks[id_] = (cb, args, kwargs) - - def get_pubsub_avatar(self, jid, node, item_id): - query = self.get_pb_retrieve_iq(jid, node, item_id) - self.connection.SendAndCallForResponse( - query, self._nec_pubsub_avatar_received, {'jid': jid}) - - def send_pb_retract(self, jid, node, id_): - """ - Delete item from a node - """ - if not self.connection or self.connected < 2: - return - query = nbxmpp.Iq('set', to=jid) - r = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) - r = r.addChild('retract', {'node': node, 'notify': '1'}) - r = r.addChild('item', {'id': id_}) - - self.connection.send(query) - - def send_pb_purge(self, jid, node): - """ - Purge node: Remove all items - """ - if not self.connection or self.connected < 2: - return - query = nbxmpp.Iq('set', to=jid) - d = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER) - d = d.addChild('purge', {'node': node}) - - self.connection.send(query) - - def send_pb_delete(self, jid, node, on_ok=None, on_fail=None): - """ - Delete node - """ - if not self.connection or self.connected < 2: - return - query = nbxmpp.Iq('set', to=jid) - d = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER) - d = d.addChild('delete', {'node': node}) - - def response(con, resp, jid, node): - if resp.getType() == 'result' and on_ok: - on_ok(jid, node) - elif on_fail: - msg = resp.getErrorMsg() - on_fail(jid, node, msg) - - self.connection.SendAndCallForResponse(query, response, {'jid': jid, - 'node': node}) - - def send_pb_create(self, jid, node, configure=False, configure_form=None): - """ - Create a new node - """ - if not self.connection or self.connected < 2: - return - query = nbxmpp.Iq('set', to=jid) - c = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB) - c = c.addChild('create', {'node': node}) - if configure: - conf = c.addChild('configure') - if configure_form is not None: - conf.addChild(node=configure_form) - - self.connection.send(query) - - def send_pb_configure(self, jid, node, form): - if not self.connection or self.connected < 2: - return - query = nbxmpp.Iq('set', to=jid) - c = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER) - c = c.addChild('configure', {'node': node}) - c.addChild(node=form) - - self.connection.send(query) - - def _PubSubCB(self, conn, stanza): - log.debug('_PubsubCB') - try: - cb, args, kwargs = self.__callbacks.pop(stanza.getID()) - cb(conn, stanza, *args, **kwargs) - except Exception: - pass - app.nec.push_incoming_event(PubsubReceivedEvent(None, - conn=self, stanza=stanza)) - - def _nec_pubsub_bookmarks_received(self, obj): - if obj.conn.name != self.name: - return - app.log('bookmarks').info('Received Bookmarks (PubSub)') - bm_jids = [b['jid'] for b in self.bookmarks] - for bm in obj.bookmarks: - if bm['jid'] not in bm_jids: - self.bookmarks.append(bm) - # We got bookmarks from pubsub, now get those from xml to merge them - self.get_bookmarks(storage_type='xml') - - def _validate_avatar_node(self, stanza): - jid = stanza.getFrom() - if jid is None: - jid = self.get_own_jid().getStripped() - else: - jid = jid.getStripped() - - if nbxmpp.isErrorNode(stanza): - raise StanzaMalformed(stanza.getErrorMsg()) - - pubsub_node = stanza.getTag('pubsub') - if pubsub_node is None: - raise StanzaMalformed('No pubsub node', stanza) - - items_node = pubsub_node.getTag('items') - if items_node is None: - raise StanzaMalformed('No items node', stanza) - - if items_node.getAttr('node') != 'urn:xmpp:avatar:data': - raise StanzaMalformed('Wrong namespace', stanza) - - item = items_node.getTag('item') - if item is None: - raise StanzaMalformed('No item node', stanza) - - sha = item.getAttr('id') - data_tag = item.getTag('data', namespace='urn:xmpp:avatar:data') - if sha is None or data_tag is None: - raise StanzaMalformed('No id attr or data node found', stanza) - - data = data_tag.getData() - if data is None: - raise StanzaMalformed('Data node empty', stanza) - - data = base64.b64decode(data.encode('utf-8')) - - return jid, sha, data - - def _nec_pubsub_avatar_received(self, conn, stanza, jid): - try: - jid, sha, data = self._validate_avatar_node(stanza) - except (StanzaMalformed, binascii.Error) as error: - app.log('avatar').warning( - 'Error loading Avatar (Pubsub): %s %s', jid, error) - return - - app.log('avatar').info( - 'Received Avatar (Pubsub): %s %s', jid, sha) - app.interface.save_avatar(data) - - if self.get_own_jid().bareMatch(jid): - app.config.set_per('accounts', self.name, 'avatar_sha', sha) - else: - own_jid = self.get_own_jid().getStripped() - app.logger.set_avatar_sha(own_jid, jid, sha) - app.contacts.set_avatar(self.name, jid, sha) - - app.interface.update_avatar(self.name, jid) - - def _PubSubErrorCB(self, conn, stanza): - log.debug('_PubsubErrorCB') - pubsub = stanza.getTag('pubsub') - if not pubsub: - return - items = pubsub.getTag('items') - if not items: - return - if items.getAttr('node') == 'storage:bookmarks': - # Receiving bookmarks from pubsub failed, so take them from xml - self.get_bookmarks(storage_type='xml') - - def request_pb_configuration(self, jid, node): - if not self.connection or self.connected < 2: - return - query = nbxmpp.Iq('get', to=jid) - e = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER) - e = e.addChild('configure', {'node': node}) - id_ = self.connection.getAnID() - query.setID(id_) - self.awaiting_answers[id_] = (PEP_CONFIG,) - self.connection.send(query) diff --git a/gajim/config.py b/gajim/config.py index 8bda9b529..c0e411b0e 100644 --- a/gajim/config.py +++ b/gajim/config.py @@ -1900,11 +1900,13 @@ class ManageBookmarksWindow: iter_ = self.treestore.append(None, [None, account, None, None, None, None, None, None]) - for bookmark in app.connections[account].bookmarks: + con = app.connections[account] + bookmarks = con.get_module('Bookmarks').bookmarks + for jid, bookmark in bookmarks.items(): if not bookmark['name']: # No name was given for this bookmark. # Use the first part of JID instead... - name = bookmark['jid'].split("@")[0] + name = jid.split("@")[0] bookmark['name'] = name # make '1', '0', 'true', 'false' (or other) to True/False @@ -1920,7 +1922,7 @@ class ManageBookmarksWindow: self.treestore.append(iter_, [ account, bookmark['name'], - bookmark['jid'], + jid, autojoin, minimize, bookmark['password'], @@ -2047,15 +2049,17 @@ class ManageBookmarksWindow: Parse the treestore data into our new bookmarks array, then send the new bookmarks to the server. """ + (model, iter_) = self.selection.get_selected() if iter_ and model.iter_parent(iter_): - #bookmark selected, check it + # bookmark selected, check it if not self.check_valid_bookmark(): return for account in self.treestore: acct = account[1] - app.connections[acct].bookmarks = [] + con = app.connections[acct] + con.get_module('Bookmarks').bookmarks = {} for bm in account.iterchildren(): # Convert True/False/None to '1' or '0' @@ -2067,13 +2071,17 @@ class ManageBookmarksWindow: nick = bm[6] # create the bookmark-dict - bmdict = { 'name': name, 'jid': jid, 'autojoin': autojoin, - 'minimize': minimize, 'password': pw, 'nick': nick, + bmdict = { + 'name': name, + 'autojoin': autojoin, + 'minimize': minimize, + 'password': pw, + 'nick': nick, 'print_status': bm[7]} - app.connections[acct].bookmarks.append(bmdict) + con.get_module('Bookmarks').bookmarks[jid] = bmdict - app.connections[acct].store_bookmarks() + con.get_module('Bookmarks').store_bookmarks() gui_menu_builder.build_bookmark_menu(acct) self.window.destroy() @@ -2900,8 +2908,10 @@ class ManagePEPServicesWindow: model, iter_ = selection.get_selected() node = model[iter_][0] our_jid = app.get_jid_from_account(self.account) - app.connections[self.account].send_pb_delete(our_jid, node, - on_ok=self.node_removed, on_fail=self.node_not_removed) + con = app.connections[self.account] + con.get_module('PubSub').send_pb_delete(our_jid, node, + on_ok=self.node_removed, + on_fail=self.node_not_removed) def on_configure_button_clicked(self, widget): selection = self.treeview.get_selection() @@ -2910,13 +2920,15 @@ class ManagePEPServicesWindow: model, iter_ = selection.get_selected() node = model[iter_][0] our_jid = app.get_jid_from_account(self.account) - app.connections[self.account].request_pb_configuration(our_jid, node) + con = app.connections[self.account] + con.get_module('PubSub').request_pb_configuration(our_jid, node) def _nec_pep_config_received(self, obj): def on_ok(form, node): form.type_ = 'submit' our_jid = app.get_jid_from_account(self.account) - app.connections[self.account].send_pb_configure(our_jid, node, form) + con = app.connections[self.account] + con.get_module('PubSub').send_pb_configure(our_jid, node, form) window = dialogs.DataFormWindow(obj.form, (on_ok, obj.node)) title = _('Configure %s') % obj.node window.set_title(title) diff --git a/gajim/dialogs.py b/gajim/dialogs.py index f8f57c369..abbec845b 100644 --- a/gajim/dialogs.py +++ b/gajim/dialogs.py @@ -2520,7 +2520,8 @@ class JoinGroupchatWindow(Gtk.ApplicationWindow): self.autojoin_switch.set_sensitive(switch.get_active()) def _add_bookmark(self, account, nickname, password): - if not app.connections[account].private_storage_supported: + con = app.connections[account] + if not con.private_storage_supported: return add_bookmark = self.bookmark_switch.get_active() @@ -2531,8 +2532,8 @@ class JoinGroupchatWindow(Gtk.ApplicationWindow): # Add as bookmark, with autojoin and not minimized name = app.get_nick_from_jid(self.room_jid) - app.interface.add_gc_bookmark( - account, name, self.room_jid, autojoin, 1, password, nickname) + con.get_module('Bookmarks').add_bookmark( + name, self.room_jid, autojoin, 1, password, nickname) def _on_destroy(self, *args): if self.minimal_mode == 0: @@ -2784,10 +2785,11 @@ class StartChatDialog(Gtk.ApplicationWindow): show_account = len(self.accounts) > 1 for account in self.accounts: self.new_groupchat_rows[account] = None - bookmarks = app.connections[account].bookmarks + con = app.connections[account] + bookmarks = con.get_module('Bookmarks').bookmarks groupchats = {} - for bookmark in bookmarks: - groupchats[bookmark['jid']] = bookmark['name'] + for jid, bookmark in bookmarks.items(): + groupchats[jid] = bookmark['name'] for jid in app.contacts.get_gc_list(account): if jid in groupchats: diff --git a/gajim/disco.py b/gajim/disco.py index 898eb92c0..2076e0af5 100644 --- a/gajim/disco.py +++ b/gajim/disco.py @@ -1757,29 +1757,29 @@ class MucBrowser(AgentBrowser): self.join_button = None def on_bookmark_button_clicked(self, *args): + con = app.connections[self.account] model, iter = self.window.services_treeview.get_selection().get_selected() if not iter: return name = app.config.get_per('accounts', self.account, 'name') room_jid = model[iter][0] bm = { - 'name': room_jid.split('@')[0], - 'jid': room_jid, - 'autojoin': '0', - 'minimize': '0', - 'password': '', - 'nick': name + 'name': room_jid.split('@')[0], + 'autojoin': '0', + 'minimize': '0', + 'password': '', + 'nick': name } - for bookmark in app.connections[self.account].bookmarks: - if bookmark['jid'] == bm['jid']: - dialogs.ErrorDialog( _('Bookmark already set'), - _('Group Chat "%s" is already in your bookmarks.') % bm['jid'], + if room_jid in con.get_module('Bookmarks').bookmarks: + dialogs.ErrorDialog( + _('Bookmark already set'), + _('Group Chat "%s" is already in your bookmarks.') % room_jid, transient_for=self.window.window) - return + return - app.connections[self.account].bookmarks.append(bm) - app.connections[self.account].store_bookmarks() + con.get_module('Bookmarks').bookmarks[room_jid] = bm + con.get_module('Bookmarks').store_bookmarks() gui_menu_builder.build_bookmark_menu(self.account) @@ -1951,8 +1951,9 @@ class DiscussionGroupsBrowser(AgentBrowser): self.subscribe_button = None self.unsubscribe_button = None - app.connections[account].send_pb_subscription_query(jid, - self._on_pep_subscriptions) + con = app.connections[account] + con.get_module('PubSub').send_pb_subscription_query( + jid, self._on_pep_subscriptions) def _create_treemodel(self): """ @@ -2122,10 +2123,11 @@ class DiscussionGroupsBrowser(AgentBrowser): model, iter_ = self.window.services_treeview.get_selection().get_selected() if iter_ is None: return - groupnode = model.get_value(iter_, 1) # 1 = groupnode + node = model.get_value(iter_, 1) # 1 = groupnode - app.connections[self.account].send_pb_subscribe(self.jid, groupnode, - self._on_pep_subscribe, groupnode) + con = app.connections[self.account] + con.get_module('PubSub').send_pb_subscribe( + self.jid, node, self._on_pep_subscribe, groupnode=node) def on_unsubscribe_button_clicked(self, widget): """ @@ -2134,10 +2136,11 @@ class DiscussionGroupsBrowser(AgentBrowser): model, iter_ = self.window.services_treeview.get_selection().get_selected() if iter_ is None: return - groupnode = model.get_value(iter_, 1) # 1 = groupnode + node = model.get_value(iter_, 1) # 1 = groupnode - app.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, - self._on_pep_unsubscribe, groupnode) + con = app.connections[self.account] + con.get_module('PubSub').send_pb_unsubscribe( + self.jid, node, self._on_pep_unsubscribe, groupnode=node) def _on_pep_subscriptions(self, conn, request): """ @@ -2168,8 +2171,6 @@ class DiscussionGroupsBrowser(AgentBrowser): # we now know subscriptions, update button states self.update_actions() - raise nbxmpp.NodeProcessed - def _on_pep_subscribe(self, conn, request, groupnode): """ We have just subscribed to a node. Update UI @@ -2184,8 +2185,6 @@ class DiscussionGroupsBrowser(AgentBrowser): self.update_actions() - raise nbxmpp.NodeProcessed - def _on_pep_unsubscribe(self, conn, request, groupnode): """ We have just unsubscribed from a node. Update UI @@ -2200,7 +2199,5 @@ class DiscussionGroupsBrowser(AgentBrowser): self.update_actions() - raise nbxmpp.NodeProcessed - # Fill the global agent type info dictionary _agent_type_info = _gen_agent_type_info() diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py index 69287f1a9..2b4e708e1 100644 --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -348,13 +348,10 @@ class GroupchatControl(ChatControlBase): self.room_jid = self.contact.jid self.nick = nick self.new_nick = '' - self.name = '' - for bm in app.connections[self.account].bookmarks: - if bm['jid'] == self.room_jid: - self.name = bm['name'] - break - if not self.name: - self.name = self.room_jid.split('@')[0] + + bm_module = app.connections[self.account].get_module('Bookmarks') + self.name = bm_module.get_name_from_bookmark(self.room_jid) + self.contact.name = self.name self.widget_set_visible(self.xml.get_object('banner_eventbox'), @@ -589,13 +586,9 @@ class GroupchatControl(ChatControlBase): # Bookmarks con = app.connections[self.account] bookmark_support = con.bookmarks_available() - not_bookmarked = True - for bm in con.bookmarks: - if bm['jid'] == self.room_jid: - not_bookmarked = False - break + bookmarked = self.room_jid in con.get_module('Bookmarks').bookmarks win.lookup_action('bookmark-' + self.control_id).set_enabled( - online and bookmark_support and not_bookmarked) + online and bookmark_support and not bookmarked) # Request Voice role = self.get_role(self.nick) @@ -712,9 +705,10 @@ class GroupchatControl(ChatControlBase): Bookmark the room, without autojoin and not minimized """ password = app.gc_passwords.get(self.room_jid, '') - app.interface.add_gc_bookmark( - self.account, self.name, self.room_jid, - '0', '0', password, self.nick) + con = app.connections[self.account] + con.get_module('Bookmarks').add_bookmark( + self.name, self.room_jid, + '1', '1', password, self.nick) self.update_actions() def _on_request_voice(self, action, param): diff --git a/gajim/groups.py b/gajim/groups.py index a2e418ee4..ecd271f71 100644 --- a/gajim/groups.py +++ b/gajim/groups.py @@ -67,7 +67,9 @@ class GroupsPostWindow: item.addChild('content', {}, [buf.get_text(buf.get_start_iter(), buf.get_end_iter(), True)]) # publish it to node - app.connections[self.account].send_pb_publish(self.servicejid, self.groupid, item, '0') + con = app.connections[self.account] + con.get_module('PubSub').send_pb_publish( + self.servicejid, self.groupid, item, '0') # close the window self.window.destroy() diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index 69254ec92..b99389ebb 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -824,17 +824,7 @@ class Interface: self.instances[account]['sub_request'][obj.jid].destroy() def handle_event_bookmarks(self, obj): - # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}]) - # We received a bookmark item from the server (JEP48) - # Auto join GC windows if necessary - - gui_menu_builder.build_bookmark_menu(obj.conn.name) - invisible_show = app.SHOW_LIST.index('invisible') - # do not autojoin if we are invisible - if obj.conn.connected == invisible_show: - return - - GLib.idle_add(self.auto_join_bookmarks, obj.conn.name) + gui_menu_builder.build_bookmark_menu(obj.account) def handle_event_file_send_error(self, account, array): jid = array[0] @@ -1728,7 +1718,6 @@ class Interface: if isinstance(ctrl, ChatControlBase): ctrl.scroll_to_end() - def join_gc_minimal(self, account, room_jid, password=None, transient_for=None): if account is not None: @@ -1738,9 +1727,10 @@ class Interface: app.interface.join_gc_room(account, room_jid, '', '') return - for bookmark in app.connections[account].bookmarks: - if bookmark['jid'] != room_jid: - continue + con = app.connections[account] + bookmarks = con.get_module('Bookmarks').bookmarks + bookmark = bookmarks.get(room_jid, None) + if bookmark is not None: app.interface.join_gc_room( account, room_jid, bookmark['nick'], bookmark['password']) return @@ -2469,51 +2459,6 @@ class Interface: return False return True - def auto_join_bookmarks(self, account): - """ - Autojoin bookmarked GCs that have 'auto join' on for this account - """ - for bm in app.connections[account].bookmarks: - if bm['autojoin'] in ('1', 'true'): - jid = bm['jid'] - # Only join non-opened groupchats. Opened one are already - # auto-joined on re-connection - if not jid in app.gc_connected[account]: - # we are not already connected - minimize = bm['minimize'] in ('1', 'true') - self.join_gc_room(account, jid, bm['nick'], - bm['password'], minimize = minimize) - - def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password, - nick): - """ - Add a bookmark for this account, sorted in bookmark list - """ - bm = { - 'name': name, - 'jid': jid, - 'autojoin': autojoin, - 'minimize': minimize, - 'password': password, - 'nick': nick - } - place_found = False - index = 0 - # check for duplicate entry and respect alpha order - for bookmark in app.connections[account].bookmarks: - if bookmark['jid'] == bm['jid']: - return - if bookmark['name'] > bm['name']: - place_found = True - break - index += 1 - if place_found: - app.connections[account].bookmarks.insert(index, bm) - else: - app.connections[account].bookmarks.append(bm) - app.connections[account].store_bookmarks() - gui_menu_builder.build_bookmark_menu(account) - # does JID exist only within a groupchat? def is_pm_contact(self, fjid, account): bare_jid = app.get_jid_without_resource(fjid) diff --git a/gajim/gui_menu_builder.py b/gajim/gui_menu_builder.py index e2b6c7fdb..3baac0f1a 100644 --- a/gajim/gui_menu_builder.py +++ b/gajim/gui_menu_builder.py @@ -172,14 +172,15 @@ show_bookmarked=False, force_resource=False): rooms2 = [] # a list of (room_jid, account) tuple r_jids = [] # list of room jids for account in connected_accounts: - for room in app.connections[account].bookmarks: - r_jid = room['jid'] - if r_jid in r_jids: + con = app.connections[account] + boomarks = con.get_module('Bookmarks').bookmarks + for jid, bookmark in boomarks.items(): + if jid in r_jids: continue - if r_jid not in app.gc_connected[account] or not \ - app.gc_connected[account][r_jid]: - rooms2.append((r_jid, account)) - r_jids.append(r_jid) + if jid not in app.gc_connected[account] or not \ + app.gc_connected[account][jid]: + rooms2.append((jid, account)) + r_jids.append(jid) if not rooms2: return @@ -675,7 +676,9 @@ def get_groupchat_menu(control_id): def get_bookmarks_menu(account, rebuild=False): - if not app.connections[account].bookmarks: + con = app.connections[account] + boomarks = con.get_module('Bookmarks').bookmarks + if not boomarks: return None menu = Gio.Menu() @@ -688,12 +691,12 @@ def get_bookmarks_menu(account, rebuild=False): # Build Bookmarks section = Gio.Menu() - for bookmark in app.connections[account].bookmarks: + for jid, bookmark in boomarks.items(): name = bookmark['name'] if not name: # No name was given for this bookmark. # Use the first part of JID instead... - name = bookmark['jid'].split("@")[0] + name = jid.split("@")[0] # Shorten long names name = (name[:42] + '..') if len(name) > 42 else name @@ -703,7 +706,7 @@ def get_bookmarks_menu(account, rebuild=False): # Create Variant Dict dict_ = {'account': GLib.Variant('s', account), - 'jid': GLib.Variant('s', bookmark['jid'])} + 'jid': GLib.Variant('s', jid)} if bookmark['nick']: dict_['nick'] = GLib.Variant('s', bookmark['nick']) if bookmark['password']: diff --git a/gajim/roster_window.py b/gajim/roster_window.py index 8ed41f184..704625fb2 100644 --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -2154,7 +2154,8 @@ class RosterWindow: auto=auto) if was_invisible and status != 'offline': # We come back from invisible, join bookmarks - app.interface.auto_join_bookmarks(account) + con = app.connections[account] + con.get_module('Bookmarks').auto_join_bookmarks() def chg_contact_status(self, contact, show, status, account): @@ -2712,9 +2713,9 @@ class RosterWindow: ### FIXME: order callbacks in itself... ################################################################################ - def on_bookmark_menuitem_activate(self, widget, account, bookmark): - app.interface.join_gc_room(account, bookmark['jid'], bookmark['nick'], - bookmark['password']) + def on_bookmark_menuitem_activate(self, widget, account, jid, bookmark): + app.interface.join_gc_room( + account, jid, bookmark['nick'], bookmark['password']) def on_info(self, widget, contact, account): """ @@ -5439,16 +5440,17 @@ class RosterWindow: gc_sub_menu.append(item) # User has at least one bookmark. - if app.connections[account].bookmarks: + con = app.connections[account] + if con.get_module('Bookmarks').bookmarks: item = Gtk.SeparatorMenuItem.new() gc_sub_menu.append(item) - for bookmark in app.connections[account].bookmarks: + for jid, bookmark in con.get_module('Bookmarks').bookmarks.items(): name = bookmark['name'] if not name: # No name was given for this bookmark. # Use the first part of JID instead... - name = bookmark['jid'].split("@")[0] + name = jid.split("@")[0] # Shorten long names name = (name[:42] + '..') if len(name) > 42 else name @@ -5456,8 +5458,9 @@ class RosterWindow: # Do not use underline. item = Gtk.MenuItem.new_with_label(name) item.set_use_underline(False) - item.connect('activate', self.on_bookmark_menuitem_activate, - account, bookmark) + item.connect( + 'activate', self.on_bookmark_menuitem_activate, + account, jid, bookmark) gc_sub_menu.append(item) def show_appropriate_context_menu(self, event, iters):