2018-07-01 02:16:33 +02:00
|
|
|
# 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/>.
|
|
|
|
|
|
|
|
# XEP-0048: Bookmarks
|
|
|
|
|
|
|
|
import logging
|
2018-09-04 23:00:29 +02:00
|
|
|
import copy
|
|
|
|
from collections import OrderedDict
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
import nbxmpp
|
|
|
|
|
|
|
|
from gajim.common import app
|
|
|
|
from gajim.common import helpers
|
|
|
|
from gajim.common.const import BookmarkStorageType
|
|
|
|
from gajim.common.nec import NetworkIncomingEvent
|
2018-09-05 00:06:59 +02:00
|
|
|
from gajim.common.modules.util import from_xs_boolean
|
|
|
|
from gajim.common.modules.util import to_xs_boolean
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
log = logging.getLogger('gajim.c.m.bookmarks')
|
|
|
|
|
2018-09-11 22:25:55 +02:00
|
|
|
NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks'
|
|
|
|
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
class Bookmarks:
|
|
|
|
def __init__(self, con):
|
|
|
|
self._con = con
|
|
|
|
self._account = con.name
|
|
|
|
self.bookmarks = {}
|
2018-07-27 15:46:02 +02:00
|
|
|
self.available = False
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
self.handlers = []
|
|
|
|
|
2018-09-04 23:00:29 +02:00
|
|
|
def get_sorted_bookmarks(self, short_name=False):
|
|
|
|
# This returns a sorted by name copy of the bookmarks
|
|
|
|
sorted_bookmarks = {}
|
|
|
|
for jid, bookmarks in self.bookmarks.items():
|
|
|
|
bookmark_copy = copy.deepcopy(bookmarks)
|
|
|
|
if not bookmark_copy['name']:
|
|
|
|
# No name was given for this bookmark
|
|
|
|
# Use the first part of JID instead
|
|
|
|
name = jid.split("@")[0]
|
|
|
|
bookmark_copy['name'] = name
|
|
|
|
|
|
|
|
if short_name:
|
|
|
|
name = bookmark_copy['name']
|
|
|
|
name = (name[:42] + '..') if len(name) > 42 else name
|
|
|
|
bookmark_copy['name'] = name
|
|
|
|
|
|
|
|
sorted_bookmarks[jid] = bookmark_copy
|
|
|
|
return OrderedDict(
|
|
|
|
sorted(sorted_bookmarks.items(),
|
|
|
|
key=lambda bookmark: bookmark[1]['name'].lower()))
|
|
|
|
|
2018-09-12 21:07:59 +02:00
|
|
|
def _pubsub_support(self) -> bool:
|
2018-07-22 12:18:24 +02:00
|
|
|
return (self._con.get_module('PEP').supported and
|
|
|
|
self._con.get_module('PubSub').publish_options)
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
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()
|
|
|
|
|
2018-09-12 21:07:59 +02:00
|
|
|
def _request_pubsub_bookmarks(self) -> None:
|
2018-07-01 02:16:33 +02:00
|
|
|
log.info('Request Bookmarks (PubSub)')
|
|
|
|
self._con.get_module('PubSub').send_pb_retrieve(
|
|
|
|
'', 'storage:bookmarks',
|
|
|
|
cb=self._pubsub_bookmarks_received)
|
|
|
|
|
2018-09-11 22:25:55 +02:00
|
|
|
def _pubsub_bookmarks_received(self, _con, stanza):
|
2018-07-01 02:16:33 +02:00
|
|
|
if not nbxmpp.isResultNode(stanza):
|
|
|
|
log.info('No pubsub bookmarks: %s', stanza.getError())
|
|
|
|
# Fallback, request private storage
|
|
|
|
self._request_private_bookmarks()
|
|
|
|
return
|
|
|
|
|
2018-07-27 15:46:02 +02:00
|
|
|
self.available = True
|
2018-07-01 02:16:33 +02:00
|
|
|
log.info('Received Bookmarks (PubSub)')
|
|
|
|
self._parse_bookmarks(stanza)
|
|
|
|
self._request_private_bookmarks()
|
|
|
|
|
2018-09-12 21:07:59 +02:00
|
|
|
def _request_private_bookmarks(self) -> None:
|
2018-07-01 02:16:33 +02:00
|
|
|
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)
|
|
|
|
|
2018-09-12 21:07:59 +02:00
|
|
|
def _private_bookmarks_received(self, stanza: nbxmpp.Iq) -> None:
|
2018-07-01 02:16:33 +02:00
|
|
|
if not nbxmpp.isResultNode(stanza):
|
|
|
|
log.info('No private bookmarks: %s', stanza.getError())
|
|
|
|
else:
|
2018-07-27 15:46:02 +02:00
|
|
|
self.available = True
|
2018-07-01 02:16:33 +02:00
|
|
|
log.info('Received Bookmarks (PrivateStorage)')
|
|
|
|
merged = self._parse_bookmarks(stanza, check_merge=True)
|
2018-07-22 12:18:24 +02:00
|
|
|
if merged and self._pubsub_support():
|
2018-07-01 02:16:33 +02:00
|
|
|
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
|
|
|
|
|
2018-09-12 21:07:59 +02:00
|
|
|
def _parse_bookmarks(self, stanza: nbxmpp.Iq,
|
|
|
|
check_merge: bool = False) -> bool:
|
2018-07-01 02:16:33 +02:00
|
|
|
merged = False
|
|
|
|
storage = self._get_storage_node(stanza)
|
|
|
|
if storage is None:
|
2018-09-12 21:07:59 +02:00
|
|
|
return False
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
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
|
2018-10-19 15:59:04 +02:00
|
|
|
|
2018-07-01 02:16:33 +02:00
|
|
|
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:
|
2018-09-11 22:25:55 +02:00
|
|
|
log.warning('Invalid JID: %s, ignoring it',
|
|
|
|
conf.getAttr('jid'))
|
2018-07-01 02:16:33 +02:00
|
|
|
continue
|
|
|
|
|
2018-10-19 15:59:04 +02:00
|
|
|
bookmark = {
|
|
|
|
'name': conf.getAttr('name'),
|
|
|
|
'password': conf.getTagData('password'),
|
|
|
|
'nick': conf.getTagData('nick'),
|
|
|
|
'print_status': print_status
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
bookmark['autojoin'] = from_xs_boolean(autojoin_val)
|
|
|
|
bookmark['minimize'] = from_xs_boolean(minimize_val)
|
|
|
|
except ValueError as error:
|
|
|
|
log.warning(error)
|
|
|
|
continue
|
|
|
|
|
2018-07-01 02:16:33 +02:00
|
|
|
if check_merge:
|
2018-07-12 21:33:34 +02:00
|
|
|
if jid in self.bookmarks:
|
|
|
|
continue
|
|
|
|
merged = True
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
log.debug('Found Bookmark: %s', jid)
|
2018-10-19 15:59:04 +02:00
|
|
|
self.bookmarks[jid] = bookmark
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
return merged
|
|
|
|
|
2018-09-12 21:07:59 +02:00
|
|
|
def _build_storage_node(self) -> nbxmpp.Node:
|
2018-07-01 02:16:33 +02:00
|
|
|
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)
|
2018-09-05 00:06:59 +02:00
|
|
|
conf_node.setAttr('autojoin', to_xs_boolean(bm['autojoin']))
|
2018-07-01 02:16:33 +02:00
|
|
|
conf_node.setAttr('name', bm['name'])
|
2018-09-05 00:06:59 +02:00
|
|
|
conf_node.setTag('minimize', namespace=NS_GAJIM_BM).setData(
|
|
|
|
to_xs_boolean(bm['minimize']))
|
2018-07-01 02:16:33 +02:00
|
|
|
# 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
|
2018-09-12 21:07:59 +02:00
|
|
|
def get_bookmark_publish_options() -> nbxmpp.Node:
|
2018-07-01 02:16:33 +02:00
|
|
|
options = nbxmpp.Node(nbxmpp.NS_DATA + ' x',
|
|
|
|
attrs={'type': 'submit'})
|
2018-09-11 22:25:55 +02:00
|
|
|
field = options.addChild('field',
|
|
|
|
attrs={'var': 'FORM_TYPE', 'type': 'hidden'})
|
|
|
|
field.setTagData('value', nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS)
|
|
|
|
field = options.addChild('field', attrs={'var': 'pubsub#access_model'})
|
|
|
|
field.setTagData('value', 'whitelist')
|
2018-07-01 02:16:33 +02:00
|
|
|
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)
|
2018-07-12 21:33:34 +02:00
|
|
|
self._private_store(storage_node)
|
2018-07-01 02:16:33 +02:00
|
|
|
elif storage_type == BookmarkStorageType.PUBSUB:
|
|
|
|
if self._pubsub_support():
|
|
|
|
self._pubsub_store(storage_node)
|
|
|
|
elif storage_type == BookmarkStorageType.PRIVATE:
|
|
|
|
self._private_store(storage_node)
|
|
|
|
|
2018-09-12 21:07:59 +02:00
|
|
|
def _pubsub_store(self, storage_node: nbxmpp.Node) -> None:
|
2018-07-01 02:16:33 +02:00
|
|
|
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)')
|
|
|
|
|
2018-09-12 21:07:59 +02:00
|
|
|
def _private_store(self, storage_node: nbxmpp.Node) -> None:
|
2018-07-01 02:16:33 +02:00
|
|
|
iq = nbxmpp.Iq('set', nbxmpp.NS_PRIVATE, payload=storage_node)
|
|
|
|
log.info('Publish Bookmarks (PrivateStorage)')
|
|
|
|
self._con.connection.SendAndCallForResponse(
|
|
|
|
iq, self._private_store_result)
|
|
|
|
|
2018-09-11 22:25:55 +02:00
|
|
|
@staticmethod
|
|
|
|
def _pubsub_store_result(_con, stanza):
|
2018-07-01 02:16:33 +02:00
|
|
|
if not nbxmpp.isResultNode(stanza):
|
|
|
|
log.error('Error: %s', stanza.getError())
|
|
|
|
return
|
|
|
|
|
2018-09-11 22:25:55 +02:00
|
|
|
@staticmethod
|
2018-09-12 21:07:59 +02:00
|
|
|
def _private_store_result(stanza: nbxmpp.Iq) -> None:
|
2018-07-01 02:16:33 +02:00
|
|
|
if not nbxmpp.isResultNode(stanza):
|
|
|
|
log.error('Error: %s', stanza.getError())
|
|
|
|
return
|
|
|
|
|
2018-09-12 21:07:59 +02:00
|
|
|
def auto_join_bookmarks(self) -> None:
|
2018-07-01 02:16:33 +02:00
|
|
|
if app.is_invisible(self._account):
|
|
|
|
return
|
|
|
|
for jid, bm in self.bookmarks.items():
|
2018-09-05 00:06:59 +02:00
|
|
|
if bm['autojoin']:
|
2018-07-01 02:16:33 +02:00
|
|
|
# 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
|
|
|
|
app.interface.join_gc_room(
|
|
|
|
self._account, jid, bm['nick'],
|
2018-09-05 00:06:59 +02:00
|
|
|
bm['password'], minimize=bm['minimize'])
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
def add_bookmark(self, name, jid, autojoin,
|
|
|
|
minimize, password, nick):
|
|
|
|
self.bookmarks[jid] = {
|
|
|
|
'name': name,
|
|
|
|
'autojoin': autojoin,
|
|
|
|
'minimize': minimize,
|
|
|
|
'password': password,
|
2018-07-29 22:26:52 +02:00
|
|
|
'nick': nick,
|
|
|
|
'print_status': None}
|
2018-07-01 02:16:33 +02:00
|
|
|
|
|
|
|
self.store_bookmarks()
|
|
|
|
app.nec.push_incoming_event(BookmarksReceivedEvent(
|
|
|
|
None, account=self._account))
|
|
|
|
|
2018-09-12 21:07:59 +02:00
|
|
|
def get_name_from_bookmark(self, jid: str) -> str:
|
2018-09-08 23:53:12 +02:00
|
|
|
fallback = jid.split('@')[0]
|
2018-07-01 02:16:33 +02:00
|
|
|
try:
|
2018-09-08 23:53:12 +02:00
|
|
|
return self.bookmarks[jid]['name'] or fallback
|
2018-07-01 02:16:33 +02:00
|
|
|
except KeyError:
|
2018-09-08 23:53:12 +02:00
|
|
|
return fallback
|
2018-07-01 02:16:33 +02:00
|
|
|
|
2018-09-12 21:07:59 +02:00
|
|
|
def purge_pubsub_bookmarks(self) -> None:
|
2018-07-01 02:16:33 +02:00
|
|
|
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'
|
2018-07-07 13:52:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
def get_instance(*args, **kwargs):
|
|
|
|
return Bookmarks(*args, **kwargs), 'Bookmarks'
|