Handle PEP bookmarks notifications

- Handle PEP bookmark notifications
- On new bookmarks with the auto join flag set, join the MUC
- Dont merge private and pubsub bookmarks
- Only use pubsub if the conversion feature is announced
This commit is contained in:
Philipp Hörist 2018-12-12 23:28:30 +01:00
parent 19b0e73f44
commit 96edd79963
3 changed files with 134 additions and 93 deletions

View File

@ -159,7 +159,7 @@ gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
nbxmpp.NS_ROSTERX, nbxmpp.NS_SECLABEL, nbxmpp.NS_HASHES_2,
nbxmpp.NS_HASHES_MD5, nbxmpp.NS_HASHES_SHA1, nbxmpp.NS_HASHES_SHA256,
nbxmpp.NS_HASHES_SHA512, nbxmpp.NS_CONFERENCE, nbxmpp.NS_CORRECT,
nbxmpp.NS_EME, 'urn:xmpp:avatar:metadata+notify']
nbxmpp.NS_EME, 'urn:xmpp:avatar:metadata+notify', 'storage:bookmarks+notify']
# Optional features gajim supports per account
gajim_optional_features = {} # type: Dict[str, List[str]]

View File

@ -172,6 +172,7 @@ class PEPEventType(IntEnum):
NICKNAME = 5
AVATAR = 6
ATOM = 7
BOOKMARKS = 8
@unique

View File

@ -14,32 +14,54 @@
# XEP-0048: Bookmarks
from typing import Any
from typing import List
from typing import Optional
import logging
import copy
from collections import OrderedDict
import nbxmpp
from gi.repository import GLib
from gajim.common import app
from gajim.common import helpers
from gajim.common.const import BookmarkStorageType
from gajim.common.nec import NetworkIncomingEvent
from gajim.common.const import PEPEventType
from gajim.common.nec import NetworkEvent
from gajim.common.exceptions import StanzaMalformed
from gajim.common.modules.pep import AbstractPEPModule
from gajim.common.modules.pep import AbstractPEPData
from gajim.common.modules.util import from_xs_boolean
from gajim.common.modules.util import to_xs_boolean
log = logging.getLogger('gajim.c.m.bookmarks')
NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks'
class Bookmarks:
class BookmarksData(AbstractPEPData):
type_ = PEPEventType.BOOKMARKS
class Bookmarks(AbstractPEPModule):
name = 'storage'
namespace = 'storage:bookmarks'
pep_class = BookmarksData
store_publish = False
_log = log
def __init__(self, con):
self._con = con
self._account = con.name
AbstractPEPModule.__init__(self, con)
self.bookmarks = {}
self.conversion = False
self.handlers = []
self._join_timeouts = []
self._request_in_progress = False
def pass_disco(self, from_, _identities, features, _data, _node):
if nbxmpp.NS_BOOKMARK_CONVERSION not in features:
@ -47,6 +69,52 @@ class Bookmarks:
self.conversion = True
log.info('Discovered Bookmarks Conversion: %s', from_)
def _extract_info(self, item):
storage = item.getTag('storage', namespace=self.namespace)
if storage is None:
raise StanzaMalformed('No storage node')
return storage
def _notification_received(self, jid: nbxmpp.JID, user_pep: Any) -> None:
if self._request_in_progress:
log.info('Ignore update, pubsub request in progress')
return
if not self._pubsub_support() or not self.conversion:
return
old_bookmarks = self._convert_to_set(self.bookmarks)
self.bookmarks = self._parse_bookmarks(user_pep.data)
self._act_on_changed_bookmarks(old_bookmarks)
app.nec.push_incoming_event(
NetworkEvent('bookmarks-received', account=self._account))
def _act_on_changed_bookmarks(self, old_bookmarks):
new_bookmarks = self._convert_to_set(self.bookmarks)
changed = new_bookmarks - old_bookmarks
if not changed:
return
join = [jid for jid, autojoin in changed if autojoin]
for jid in join:
log.info('Schedule autojoin in 10s for: %s', jid)
# If another client creates a MUC, the MUC is locked until the
# configuration is finished. Give the user some time to finish
# the configuration.
timeout_id = GLib.timeout_add_seconds(
10, self._join_with_timeout, join)
self._join_timeouts.append(timeout_id)
# TODO: leave mucs
# leave = [jid for jid, autojoin in changed if not autojoin]
@staticmethod
def _convert_to_set(bookmarks):
set_ = set()
for jid in bookmarks:
set_.add((jid, bookmarks[jid]['autojoin']))
return set_
def get_sorted_bookmarks(self, short_name=False):
# This returns a sorted by name copy of the bookmarks
sorted_bookmarks = {}
@ -72,65 +140,43 @@ class Bookmarks:
return (self._con.get_module('PEP').supported and
self._con.get_module('PubSub').publish_options)
def get_bookmarks(self, storage_type=None):
def get_bookmarks(self):
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()
if self._pubsub_support() and self.conversion:
self._request_pubsub_bookmarks()
else:
log.info('Request Bookmarks (PrivateStorage)')
self._request_private_bookmarks()
def _request_pubsub_bookmarks(self) -> None:
log.info('Request Bookmarks (PubSub)')
self._request_in_progress = True
self._con.get_module('PubSub').send_pb_retrieve(
'', 'storage:bookmarks',
cb=self._pubsub_bookmarks_received)
def _pubsub_bookmarks_received(self, _con, stanza):
if not nbxmpp.isResultNode(stanza):
log.info('No pubsub bookmarks: %s', stanza.getError())
# Fallback, request private storage, only if server
# doesnt have bookmark conversion
if not self.conversion:
self._request_private_bookmarks()
return
log.info('Received Bookmarks (PubSub)')
self._parse_bookmarks(stanza)
if not self.conversion:
# If server does not have bookmark conversion, request private
# storage and try to merge the bookmarks
self._request_private_bookmarks()
'', 'storage:bookmarks', cb=self._bookmarks_received)
def _request_private_bookmarks(self) -> None:
if not app.account_is_connected(self._account):
return
self._request_in_progress = True
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)
iq, self._bookmarks_received, {})
def _private_bookmarks_received(self, stanza: nbxmpp.Iq) -> None:
def _bookmarks_received(self, _con, stanza):
self._request_in_progress = False
if not nbxmpp.isResultNode(stanza):
log.info('No private bookmarks: %s', stanza.getError())
log.info('No bookmarks found: %s', stanza.getError())
else:
log.info('Received Bookmarks (PrivateStorage)')
merged = self._parse_bookmarks(stanza, check_merge=True)
if merged and self._pubsub_support():
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))
log.info('Received Bookmarks')
storage = self._get_storage_node(stanza)
if storage is not None:
self.bookmarks = self._parse_bookmarks(storage)
self.auto_join_bookmarks()
app.nec.push_incoming_event(
NetworkEvent('bookmarks-received', account=self._account))
@staticmethod
def _get_storage_node(stanza):
@ -163,13 +209,9 @@ class Bookmarks:
return
return storage
def _parse_bookmarks(self, stanza: nbxmpp.Iq,
check_merge: bool = False) -> bool:
merged = False
storage = self._get_storage_node(stanza)
if storage is None:
return False
@staticmethod
def _parse_bookmarks(storage: nbxmpp.Node) -> bool:
bookmarks = {}
confs = storage.getTags('conference')
for conf in confs:
autojoin_val = conf.getAttr('autojoin')
@ -177,18 +219,14 @@ class Bookmarks:
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
if not minimize_val:
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')
print_status = None
else:
print_status = print_status.getData()
@ -213,20 +251,16 @@ class Bookmarks:
log.warning(error)
continue
if check_merge:
if jid in self.bookmarks:
continue
merged = True
log.debug('Found Bookmark: %s', jid)
self.bookmarks[jid] = bookmark
bookmarks[jid] = bookmark
return merged
return bookmarks
def _build_storage_node(self) -> nbxmpp.Node:
@staticmethod
def _build_storage_node(bookmarks):
storage_node = nbxmpp.Node(
tag='storage', attrs={'xmlns': 'storage:bookmarks'})
for jid, bm in self.bookmarks.items():
for jid, bm in bookmarks.items():
conf_node = storage_node.addChild(name="conference")
conf_node.setAttr('jid', jid)
conf_node.setAttr('autojoin', to_xs_boolean(bm['autojoin']))
@ -246,6 +280,9 @@ class Bookmarks:
namespace=NS_GAJIM_BM).setData(bm['print_status'])
return storage_node
def _build_node(self, _data):
pass
@staticmethod
def get_bookmark_publish_options() -> nbxmpp.Node:
options = nbxmpp.Node(nbxmpp.NS_DATA + ' x',
@ -257,23 +294,14 @@ class Bookmarks:
field.setTagData('value', 'whitelist')
return options
def store_bookmarks(self, storage_type=None):
def store_bookmarks(self):
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)
if self.conversion:
# Only push to either pubsub or private storage
return
self._private_store(storage_node)
elif storage_type == BookmarkStorageType.PUBSUB:
if self._pubsub_support():
self._pubsub_store(storage_node)
elif storage_type == BookmarkStorageType.PRIVATE:
storage_node = self._build_storage_node(self.bookmarks)
if self._pubsub_support() and self.conversion:
self._pubsub_store(storage_node)
else:
self._private_store(storage_node)
def _pubsub_store(self, storage_node: nbxmpp.Node) -> None:
@ -301,18 +329,27 @@ class Bookmarks:
log.error('Error: %s', stanza.getError())
return
def auto_join_bookmarks(self) -> None:
def _join_with_timeout(self, bookmarks: Optional[List[str]] = None) -> None:
self._join_timeouts.pop(0)
self.auto_join_bookmarks(bookmarks)
def auto_join_bookmarks(self, bookmarks: Optional[List[str]] = None) -> None:
if app.is_invisible(self._account):
return
for jid, bm in self.bookmarks.items():
if bm['autojoin']:
if bookmarks is None:
bookmarks = self.bookmarks.keys()
for jid in bookmarks:
bookmark = self.bookmarks[jid]
if bookmark['autojoin']:
# 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
log.info('Autojoin Bookmark: %s', jid)
app.interface.join_gc_room(
self._account, jid, bm['nick'],
bm['password'], minimize=bm['minimize'])
self._account, jid, bookmark['nick'],
bookmark['password'], minimize=bookmark['minimize'])
def add_bookmark(self, name, jid, autojoin,
minimize, password, nick):
@ -325,8 +362,8 @@ class Bookmarks:
'print_status': None}
self.store_bookmarks()
app.nec.push_incoming_event(BookmarksReceivedEvent(
None, account=self._account))
app.nec.push_incoming_event(
NetworkEvent('bookmarks-received', account=self._account))
def get_name_from_bookmark(self, jid: str) -> str:
fallback = jid.split('@')[0]
@ -341,9 +378,12 @@ class Bookmarks:
self._con.get_module('PubSub').send_pb_purge('', 'storage:bookmarks')
self._con.get_module('PubSub').send_pb_delete('', 'storage:bookmarks')
def _remove_timeouts(self):
for _id in self._join_timeouts:
GLib.source_remove(_id)
class BookmarksReceivedEvent(NetworkIncomingEvent):
name = 'bookmarks-received'
def cleanup(self):
self._remove_timeouts()
def get_instance(*args, **kwargs):