Philipp Hörist pushed to branch master at gajim / gajim
Commits: ccb3c2de by Philipp Hörist at 2018-07-06T19:55:13+02:00 Move rest of UserAvatar into new module
- - - - - 6a363678 by Philipp Hörist at 2018-07-06T20:05:57+02:00 Dont handle all pep events
- - - - - 70661b70 by Philipp Hörist at 2018-07-06T20:12:50+02:00 Rename method to something more appropriate
- - - - - d45fa13e by Philipp Hörist at 2018-07-06T21:02:06+02:00 Refactor atom into new module and disable it
Gajim lacks a good UI for microblogging
Fixes #9218
- - - - -
10 changed files:
- − gajim/common/atom.py - gajim/common/connection_handlers_events.py - gajim/common/const.py - + gajim/common/modules/atom.py - gajim/common/modules/pep.py - gajim/common/modules/user_avatar.py - gajim/common/modules/user_nickname.py - gajim/common/pep.py - gajim/gui_interface.py - gajim/roster_window.py
Changes:
===================================== gajim/common/atom.py deleted ===================================== --- a/gajim/common/atom.py +++ /dev/null @@ -1,176 +0,0 @@ -# -*- coding: utf-8 -*- -## src/common/atom.py -## -## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org> -## Tomasz Melcer <liori AT exroot.org> -## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org> -## -## 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/. -## - -""" -Atom (rfc 4287) feed parser, used to read data from atom-over-pubsub transports -and services. Very simple. Actually implements only atom:entry. Implement more features -if you need -""" - -# suggestion: rewrite functions that return dates to return standard python time tuples, -# exteneded to contain timezone - -import nbxmpp -import time - -class PersonConstruct(nbxmpp.Node, object): - """ - Not used for now, as we don't need authors/contributors in pubsub.com feeds. - They rarely exist there - """ - - def __init__(self, node): - ''' Create person construct from node. ''' - nbxmpp.Node.__init__(self, node=node) - - def get_name(self): - return self.getTagData('name') - - name = property(get_name, None, None, - '''Conveys a human-readable name for the person. Should not be None, - although some badly generated atom feeds don't put anything here - (this is non-standard behavior, still pubsub.com sometimes does that.)''') - - def get_uri(self): - return self.getTagData('uri') - - uri = property(get_uri, None, None, - '''Conveys an IRI associated with the person. Might be None when not set.''') - - def get_email(self): - return self.getTagData('email') - - email = property(get_email, None, None, - '''Conveys an e-mail address associated with the person. Might be None when - not set.''') - -class Entry(nbxmpp.Node, object): - def __init__(self, node=None): - nbxmpp.Node.__init__(self, 'entry', node=node) - - def __repr__(self): - return '<Atom:Entry object of id="%r">' % self.getAttr('id') - -class OldEntry(nbxmpp.Node, object): - """ - Parser for feeds from pubsub.com. They use old Atom 0.3 format with their - extensions - """ - - def __init__(self, node=None): - ''' Create new Atom 0.3 entry object. ''' - nbxmpp.Node.__init__(self, 'entry', node=node) - - def __repr__(self): - return '<Atom0.3:Entry object of id="%r">' % self.getAttr('id') - - def get_feed_title(self): - """ - Return title of feed, where the entry was created. The result is the feed - name concatenated with source-feed title - """ - if self.parent is not None: - main_feed = self.parent.getTagData('title') - else: - main_feed = None - - if self.getTag('feed') is not None: - source_feed = self.getTag('feed').getTagData('title') - else: - source_feed = None - - - if main_feed is not None and source_feed is not None: - return '%s: %s' % (main_feed, source_feed) - elif main_feed is not None: - return main_feed - elif source_feed is not None: - return source_feed - else: - return '' - - feed_title = property(get_feed_title, None, None, - ''' Title of feed. It is built from entry''s original feed title and title of feed - which delivered this entry. ''') - - def get_feed_link(self): - """ - Get source link - """ - try: - return self.getTag('feed').getTags('link', {'rel':'alternate'})[1].getData() - except Exception: - return None - - feed_link = property(get_feed_link, None, None, - ''' Link to main webpage of the feed. ''') - - def get_title(self): - """ - Get an entry's title - """ - return self.getTagData('title') - - title = property(get_title, None, None, - ''' Entry's title. ''') - - def get_uri(self): - """ - Get the uri the entry points to (entry's first link element with - rel='alternate' or without rel attribute) - """ - for element in self.getTags('link'): - if 'rel' in element.attrs and element.attrs['rel']!='alternate': continue - try: - return element.attrs['href'] - except AttributeError: - pass - return None - - uri = property(get_uri, None, None, - ''' URI that is pointed by the entry. ''') - - def get_updated(self): - """ - Get the time the entry was updated last time - - This should be standarized, but pubsub.com sends it in human-readable - format. We won't try to parse it. (Atom 0.3 uses the word «modified» for - that). - - If there's no time given in the entry, we try with <published> - and <issued> elements. - """ - for name in ('updated', 'modified', 'published', 'issued'): - date = self.getTagData(name) - if date is not None: break - - if date is None: - # it is not in the standard format - return time.asctime() - - return date - - updated = property(get_updated, None, None, - ''' Last significant modification time. ''') - - feed_tagline = ''
===================================== gajim/common/connection_handlers_events.py ===================================== --- a/gajim/common/connection_handlers_events.py +++ b/gajim/common/connection_handlers_events.py @@ -22,7 +22,6 @@ # pylint: disable=attribute-defined-outside-init
from calendar import timegm -import datetime import hashlib import hmac import logging @@ -33,7 +32,6 @@ import OpenSSL.crypto import nbxmpp from nbxmpp.protocol import NS_CHATSTATES
-from gajim.common import atom from gajim.common import nec from gajim.common import helpers from gajim.common import app @@ -1759,24 +1757,6 @@ class PEPReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.pep_type = pep.type_ return True
- items = self.event_tag.getTag('items') - if items: - # for each entry in feed (there shouldn't be more than one, but to - # be sure... - for item in items.getTags('item'): - entry = item.getTag('entry', namespace=nbxmpp.NS_ATOM) - if entry: - app.nec.push_incoming_event(AtomEntryReceived(None, - conn=self.conn, node=entry)) - -class AtomEntryReceived(nec.NetworkIncomingEvent): - name = 'atom-entry-received' - base_network_events = [] - - def generate(self): - self.atom_entry = atom.OldEntry(node=self.node) - return True - class PlainConnectionEvent(nec.NetworkIncomingEvent): name = 'plain-connection' base_network_events = []
===================================== gajim/common/const.py ===================================== --- a/gajim/common/const.py +++ b/gajim/common/const.py @@ -152,6 +152,8 @@ class PEPEventType(IntEnum): MOOD = 2 LOCATION = 3 NICKNAME = 4 + AVATAR = 5 + ATOM = 6
ACTIVITIES = {
===================================== gajim/common/modules/atom.py ===================================== --- /dev/null +++ b/gajim/common/modules/atom.py @@ -0,0 +1,226 @@ +# Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org> +# Tomasz Melcer <liori AT exroot.org> +# Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org> +# +# 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/. + +""" +Atom (rfc 4287) feed parser, used to read data from atom-over-pubsub transports +and services. Very simple. Actually implements only atom:entry. +Implement more features if you need +""" + +# XEP-0277: Microblogging over XMPP + +# Module is disabled for now because Gajim lacks a good UI for that +# register the module in connection.py with register_module() to activate again + +import logging +import nbxmpp +import time + +from gajim.common.const import PEPEventType +from gajim.common.exceptions import StanzaMalformed +from gajim.common.modules.pep import AbstractPEPModule, AbstractPEPData + +log = logging.getLogger('gajim.c.m.atom') + + +class AtomData(AbstractPEPData): + + type_ = PEPEventType.ATOM + + def __init__(self, atom): + self._pep_specific_data = atom + + def get_entry(self): + return self._pep_specific_data + + +class Atom(AbstractPEPModule): + + name = 'atom' + namespace = 'urn:xmpp:microblog:0' + pep_class = AtomData + store_publish = False + _log = log + + def __init__(self, con): + AbstractPEPModule.__init__(self, con, con.name) + + self.handlers = [] + + def _extract_info(self, item): + entry = item.getTag('entry', namespace=nbxmpp.NS_ATOM) + if entry is None: + StanzaMalformed('no entry node') + + return OldEntry(node=entry) or None + + +class PersonConstruct(nbxmpp.Node, object): + """ + Not used for now, as we don't need authors/contributors + in pubsub.com feeds. They rarely exist there + """ + + def __init__(self, node): + ''' Create person construct from node. ''' + nbxmpp.Node.__init__(self, node=node) + + def get_name(self): + return self.getTagData('name') + + name = property( + get_name, None, None, + '''Conveys a human-readable name for the person. Should not be None, + although some badly generated atom feeds don't put anything here + (this is non-standard behavior, still pubsub.com sometimes does that.)''') + + def get_uri(self): + return self.getTagData('uri') + + uri = property( + get_uri, None, None, + '''Conveys an IRI associated with the person. + Might be None when not set.''') + + def get_email(self): + return self.getTagData('email') + + email = property( + get_email, None, None, + '''Conveys an e-mail address associated with the person. Might be None when + not set.''') + + +class Entry(nbxmpp.Node, object): + def __init__(self, node=None): + nbxmpp.Node.__init__(self, 'entry', node=node) + + def __repr__(self): + return '<Atom:Entry object of id="%r">' % self.getAttr('id') + + +class OldEntry(nbxmpp.Node, object): + """ + Parser for feeds from pubsub.com. They use old Atom 0.3 format with their + extensions + """ + + def __init__(self, node=None): + ''' Create new Atom 0.3 entry object. ''' + nbxmpp.Node.__init__(self, 'entry', node=node) + + def __repr__(self): + return '<Atom0.3:Entry object of id="%r">' % self.getAttr('id') + + def get_feed_title(self): + """ + Return title of feed, where the entry was created. + The result is the feed name concatenated with source-feed title + """ + if self.parent is not None: + main_feed = self.parent.getTagData('title') + else: + main_feed = None + + if self.getTag('feed') is not None: + source_feed = self.getTag('feed').getTagData('title') + else: + source_feed = None + + if main_feed is not None and source_feed is not None: + return '%s: %s' % (main_feed, source_feed) + elif main_feed is not None: + return main_feed + elif source_feed is not None: + return source_feed + else: + return '' + + feed_title = property( + get_feed_title, None, None, + ''' Title of feed. It is built from entry''s original feed title + and title of feed which delivered this entry. ''') + + def get_feed_link(self): + """ + Get source link + """ + try: + return self.getTag('feed').getTags('link', {'rel': 'alternate'})[1].getData() + except Exception: + return None + + feed_link = property( + get_feed_link, None, None, + ''' Link to main webpage of the feed. ''') + + def get_title(self): + """ + Get an entry's title + """ + return self.getTagData('title') + + title = property( + get_title, None, None, + ''' Entry's title. ''') + + def get_uri(self): + """ + Get the uri the entry points to (entry's first link element with + rel='alternate' or without rel attribute) + """ + for element in self.getTags('link'): + if 'rel' in element.attrs and element.attrs['rel'] != 'alternate': + continue + try: + return element.attrs['href'] + except AttributeError: + pass + return None + + uri = property( + get_uri, None, None, + ''' URI that is pointed by the entry. ''') + + def get_updated(self): + """ + Get the time the entry was updated last time + + This should be standarized, but pubsub.com sends it in human-readable + format. We won't try to parse it. + (Atom 0.3 uses the word «modified» for that). + + If there's no time given in the entry, we try with <published> + and <issued> elements. + """ + for name in ('updated', 'modified', 'published', 'issued'): + date = self.getTagData(name) + if date is not None: + break + + if date is None: + # it is not in the standard format + return time.asctime() + + return date + + updated = property( + get_updated, None, None, + ''' Last significant modification time. ''') + + feed_tagline = ''
===================================== gajim/common/modules/pep.py ===================================== --- a/gajim/common/modules/pep.py +++ b/gajim/common/modules/pep.py @@ -134,14 +134,14 @@ class AbstractPEPModule: raise NotImplementedError
def _push_event(self, jid, user_pep): - self._update_contacts(jid, user_pep) + self._notification_received(jid, user_pep) app.nec.push_incoming_event( PEPReceivedEvent(None, conn=self._con, jid=str(jid), pep_type=self.name, user_pep=user_pep))
- def _update_contacts(self, jid, user_pep): + def _notification_received(self, jid, user_pep): for contact in app.contacts.get_contacts(self._account, str(jid)): if user_pep: contact.pep[self.name] = user_pep
===================================== gajim/common/modules/user_avatar.py ===================================== --- a/gajim/common/modules/user_avatar.py +++ b/gajim/common/modules/user_avatar.py @@ -21,15 +21,31 @@ import binascii import nbxmpp
from gajim.common import app +from gajim.common.const import PEPEventType from gajim.common.exceptions import StanzaMalformed +from gajim.common.modules.pep import AbstractPEPModule, AbstractPEPData
log = logging.getLogger('gajim.c.m.user_avatar')
-class UserAvatar: +class UserAvatarData(AbstractPEPData): + + type_ = PEPEventType.AVATAR + + def __init__(self, avatar): + self._pep_specific_data = avatar + + +class UserAvatar(AbstractPEPModule): + + name = 'user-avatar' + namespace = 'urn:xmpp:avatar:metadata' + pep_class = UserAvatarData + store_publish = False + _log = log + def __init__(self, con): - self._con = con - self._account = con.name + AbstractPEPModule.__init__(self, con, con.name)
self.handlers = []
@@ -83,7 +99,7 @@ class UserAvatar: log.warning('Error: %s %s', stanza.getFrom(), error) return
- log.info('Received: %s %s', jid, sha) + log.info('Received Avatar: %s %s', jid, sha) app.interface.save_avatar(data)
if self._con.get_own_jid().bareMatch(jid): @@ -94,3 +110,45 @@ class UserAvatar: app.contacts.set_avatar(self._account, jid, sha)
app.interface.update_avatar(self._account, jid) + + def _extract_info(self, item): + metadata = item.getTag('metadata', namespace=self.namespace) + if metadata is None: + raise StanzaMalformed('No metadata node') + + info = metadata.getTags('info', one=True) + if not info: + return None + + avatar = info.getAttrs() + return avatar or None + + def _notification_received(self, jid, user_pep): + avatar = user_pep._pep_specific_data + own_jid = self._con.get_own_jid() + if avatar is None: + # Remove avatar + log.info('Remove: %s', jid) + app.contacts.set_avatar(self._account, str(jid), None) + app.logger.set_avatar_sha(own_jid.getStripped(), str(jid), None) + app.interface.update_avatar(self._account, str(jid)) + else: + if own_jid.bareMatch(jid): + sha = app.config.get_per( + 'accounts', self._account, 'avatar_sha') + else: + sha = app.contacts.get_avatar_sha(self._account, str(jid)) + + if sha == avatar['id']: + log.info('Avatar already known: %s %s', + jid, avatar['id']) + return + self.get_pubsub_avatar(jid, avatar['id']) + + def send(self, data): + # Not implemented yet + return + + def retract(self): + # Not implemented yet + return
===================================== gajim/common/modules/user_nickname.py ===================================== --- a/gajim/common/modules/user_nickname.py +++ b/gajim/common/modules/user_nickname.py @@ -66,7 +66,7 @@ class UserNickname(AbstractPEPModule): item.addData(data) return item
- def _update_contacts(self, jid, user_pep): + def _notification_received(self, jid, user_pep): for contact in app.contacts.get_contacts(self._account, str(jid)): contact.contact_name = user_pep.get_nick()
===================================== gajim/common/pep.py ===================================== --- a/gajim/common/pep.py +++ b/gajim/common/pep.py @@ -80,48 +80,4 @@ class AbstractPEP(object): pass
-class AvatarNotificationPEP(AbstractPEP): - '''XEP-0084: Avatars''' - - type_ = 'avatar-notification' - namespace = 'urn:xmpp:avatar:metadata' - - def _extract_info(self, items): - self.avatar = None - for item in items.getTags('item'): - metadata = item.getTag('metadata') - if metadata is None: - 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: - self.avatar = info.getAttrs() - break - - return (None, False) - - def _on_receive(self, jid, account): - con = app.connections[account] - if self.avatar is None: - # Remove avatar - 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('c.m.user_avatar').info( - 'Update: %s %s', jid, self.avatar['id']) - if sha == self.avatar['id']: - app.log('c.m.user_avatar').info( - 'Avatar already known: %s %s', - jid, self.avatar['id']) - return - con.get_module('UserAvatar').get_pubsub_avatar( - jid, self.avatar['id']) - - -SUPPORTED_PERSONAL_USER_EVENTS = [ - AvatarNotificationPEP] +SUPPORTED_PERSONAL_USER_EVENTS = []
===================================== gajim/gui_interface.py ===================================== --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -1166,7 +1166,10 @@ class Interface:
@staticmethod def handle_atom_entry(obj): - AtomWindow.newAtomEntry(obj.atom_entry) + if obj != PEPEventType.ATOM: + return + if obj.get_entry(): + AtomWindow.newAtomEntry(obj.get_entry())
def handle_event_zc_name_conflict(self, obj): def on_ok(new_name): @@ -1503,7 +1506,7 @@ class Interface: self.handlers = { 'DB_ERROR': [self.handle_event_db_error], 'FILE_SEND_ERROR': [self.handle_event_file_send_error], - 'atom-entry-received': [self.handle_atom_entry], + 'pep-received': [self.handle_atom_entry], 'bad-gpg-passphrase': [self.handle_event_bad_gpg_passphrase], 'bookmarks-received': [self.handle_event_bookmarks], 'client-cert-passphrase': [
===================================== gajim/roster_window.py ===================================== --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -64,6 +64,7 @@ from gajim.common import helpers from gajim.common import idle from gajim.common.exceptions import GajimGeneralException from gajim.common import i18n +from gajim.common.const import PEPEventType if app.is_installed('GEOCLUE'): from gajim.common import location_listener from gajim.common import ged @@ -2648,6 +2649,13 @@ class RosterWindow: self.remove_contact(jid, obj.conn.name, backend=True)
def _nec_pep_received(self, obj): + if obj.user_pep.type_ not in (PEPEventType.ACTIVITY, + PEPEventType.TUNE, + PEPEventType.MOOD, + PEPEventType.LOCATION, + PEPEventType.NICKNAME): + return + if obj.jid == app.get_jid_from_account(obj.conn.name): self.draw_account(obj.conn.name)
View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/0a6b2126b34dd9d627e203a630c185f2df...