9578053 Jan 22 2022 distfiles.gentoo.org/distfiles/gajim-1.3.3-2.tar.gz
This commit is contained in:
parent
a5b3822651
commit
4c1b226bff
1045 changed files with 753037 additions and 18 deletions
379
gajim/common/modules/roster.py
Normal file
379
gajim/common/modules/roster.py
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
# 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/>.
|
||||
|
||||
# Roster
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
import nbxmpp
|
||||
from nbxmpp.namespaces import Namespace
|
||||
from nbxmpp.structs import StanzaHandler
|
||||
|
||||
from gajim.common import app
|
||||
from gajim.common.nec import NetworkEvent
|
||||
from gajim.common.modules.base import BaseModule
|
||||
|
||||
RosterItem = namedtuple('RosterItem', 'jid data')
|
||||
|
||||
|
||||
class Roster(BaseModule):
|
||||
def __init__(self, con):
|
||||
BaseModule.__init__(self, con)
|
||||
|
||||
self.handlers = [
|
||||
StanzaHandler(name='iq',
|
||||
callback=self._roster_push_received,
|
||||
typ='set',
|
||||
ns=Namespace.ROSTER),
|
||||
StanzaHandler(name='presence',
|
||||
callback=self._presence_received),
|
||||
]
|
||||
|
||||
self._data = {}
|
||||
self._set = None
|
||||
|
||||
def load_roster(self):
|
||||
self._log.info('Load from database')
|
||||
data = app.storage.cache.load_roster(self._account)
|
||||
if data:
|
||||
self.set_raw(data)
|
||||
for jid, item in self._data.items():
|
||||
self._log.debug('%s: %s', jid, item)
|
||||
app.nec.push_incoming_event(NetworkEvent(
|
||||
'roster-info',
|
||||
conn=self._con,
|
||||
jid=jid,
|
||||
nickname=item['name'],
|
||||
sub=item['subscription'],
|
||||
ask=item['ask'],
|
||||
groups=item['groups'],
|
||||
avatar_sha=item.get('avatar_sha')))
|
||||
else:
|
||||
self._log.info('Database empty, reset roster version')
|
||||
app.settings.set_account_setting(
|
||||
self._account, 'roster_version', '')
|
||||
|
||||
app.nec.push_incoming_event(NetworkEvent(
|
||||
'roster-received',
|
||||
conn=self._con,
|
||||
roster=self._data.copy(),
|
||||
received_from_server=False))
|
||||
|
||||
def _store_roster(self):
|
||||
app.storage.cache.store_roster(self._account, self._data)
|
||||
|
||||
def request_roster(self):
|
||||
version = None
|
||||
features = self._con.connection.features
|
||||
if features.has_roster_version:
|
||||
version = app.settings.get_account_setting(self._account,
|
||||
'roster_version')
|
||||
|
||||
self._log.info('Requested from server')
|
||||
iq = nbxmpp.Iq('get', Namespace.ROSTER)
|
||||
if version is not None:
|
||||
iq.setTagAttr('query', 'ver', version)
|
||||
self._log.info('Request version: %s', version)
|
||||
self._con.connection.SendAndCallForResponse(
|
||||
iq, self._roster_received)
|
||||
|
||||
def _roster_received(self, _nbxmpp_client, stanza):
|
||||
if not nbxmpp.isResultNode(stanza):
|
||||
self._log.warning('Unable to retrieve roster: %s',
|
||||
stanza.getError())
|
||||
else:
|
||||
self._log.info('Received Roster')
|
||||
received_from_server = False
|
||||
if stanza.getTag('query') is not None:
|
||||
# clear Roster
|
||||
self._data = {}
|
||||
version = self._parse_roster(stanza)
|
||||
|
||||
self._log.info('New version: %s', version)
|
||||
self._store_roster()
|
||||
app.settings.set_account_setting(self._account,
|
||||
'roster_version',
|
||||
version)
|
||||
|
||||
received_from_server = True
|
||||
|
||||
app.nec.push_incoming_event(NetworkEvent(
|
||||
'roster-received',
|
||||
conn=self._con,
|
||||
roster=self._data.copy(),
|
||||
received_from_server=received_from_server))
|
||||
|
||||
self._con.connect_machine()
|
||||
|
||||
def _roster_push_received(self, _con, stanza, _properties):
|
||||
self._log.info('Push received')
|
||||
|
||||
sender = stanza.getFrom()
|
||||
if sender is not None:
|
||||
if not self._con.get_own_jid().bare_match(sender):
|
||||
self._log.warning('Wrong JID %s', stanza.getFrom())
|
||||
return
|
||||
|
||||
push_items, version = self._parse_push(stanza)
|
||||
|
||||
self._ack_roster_push(stanza)
|
||||
|
||||
for item in push_items:
|
||||
attrs = item.data
|
||||
app.nec.push_incoming_event(NetworkEvent(
|
||||
'roster-info',
|
||||
conn=self._con,
|
||||
jid=item.jid,
|
||||
nickname=attrs['name'],
|
||||
sub=attrs['subscription'],
|
||||
ask=attrs['ask'],
|
||||
groups=attrs['groups'],
|
||||
avatar_sha=None))
|
||||
|
||||
self._store_roster()
|
||||
|
||||
self._log.info('New version: %s', version)
|
||||
app.settings.set_account_setting(self._account,
|
||||
'roster_version',
|
||||
version)
|
||||
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
def _parse_roster(self, stanza):
|
||||
query = stanza.getTag('query')
|
||||
version = query.getAttr('ver')
|
||||
|
||||
for item in query.getTags('item'):
|
||||
jid = item.getAttr('jid')
|
||||
self._data[jid] = self._get_item_attrs(item)
|
||||
self._log.info('Item %s: %s', jid, self._data[jid])
|
||||
return version
|
||||
|
||||
@staticmethod
|
||||
def _get_item_attrs(item, update=False):
|
||||
'''
|
||||
update: True
|
||||
Omit avatar_sha from the returned attrs
|
||||
|
||||
update: False
|
||||
Include the default value from avatar_sha in the returned attrs
|
||||
'''
|
||||
|
||||
default_attrs = {'name': None,
|
||||
'ask': None,
|
||||
'subscription': None,
|
||||
'groups': []}
|
||||
|
||||
if not update:
|
||||
default_attrs['avatar_sha'] = None
|
||||
|
||||
attrs = item.getAttrs()
|
||||
del attrs['jid']
|
||||
groups = {group.getData() for group in item.getTags('group')}
|
||||
attrs['groups'] = list(groups)
|
||||
|
||||
default_attrs.update(attrs)
|
||||
return default_attrs
|
||||
|
||||
def _parse_push(self, stanza):
|
||||
query = stanza.getTag('query')
|
||||
version = query.getAttr('ver')
|
||||
push_items = []
|
||||
|
||||
for item in query.getTags('item'):
|
||||
push_items.append(self._update_roster_item(item))
|
||||
for item in push_items:
|
||||
self._log.info('Push: %s', item)
|
||||
return push_items, version
|
||||
|
||||
def _update_roster_item(self, item):
|
||||
jid = item.getAttr('jid')
|
||||
|
||||
if item.getAttr('subscription') == 'remove':
|
||||
self._data.pop(jid, None)
|
||||
attrs = self._get_item_attrs(item)
|
||||
return RosterItem(jid, attrs)
|
||||
|
||||
if jid not in self._data:
|
||||
self._data[jid] = self._get_item_attrs(item)
|
||||
else:
|
||||
self._data[jid].update(self._get_item_attrs(item, update=True))
|
||||
|
||||
return RosterItem(jid, self._data[jid])
|
||||
|
||||
def _ack_roster_push(self, stanza):
|
||||
iq = nbxmpp.Iq('result',
|
||||
to=stanza.getFrom(),
|
||||
frm=stanza.getTo(),
|
||||
attrs={'id': stanza.getID()})
|
||||
self._con.connection.send(iq)
|
||||
|
||||
def _presence_received(self, _con, pres, _properties):
|
||||
'''
|
||||
Add contacts that request subscription to our internal
|
||||
roster and also to the database. The contact is put into the
|
||||
'Not in contact list' group and because we save it to the database
|
||||
it is also after a restart available.
|
||||
'''
|
||||
|
||||
if pres.getType() != 'subscribe':
|
||||
return
|
||||
|
||||
jid = pres.getFrom().bare
|
||||
|
||||
if jid in self._data:
|
||||
return
|
||||
|
||||
self._log.info('Add Contact from presence %s', jid)
|
||||
self._data[jid] = {'name': None,
|
||||
'ask': None,
|
||||
'subscription':
|
||||
'none',
|
||||
'groups': ['Not in contact list']}
|
||||
|
||||
self._store_roster()
|
||||
|
||||
def _get_item_data(self, jid, dataname):
|
||||
"""
|
||||
Return specific jid's representation in internal format.
|
||||
"""
|
||||
jid = jid[:(jid + '/').find('/')]
|
||||
return self._data[jid][dataname]
|
||||
|
||||
def del_item(self, jid):
|
||||
"""
|
||||
Delete contact 'jid' from roster
|
||||
"""
|
||||
self._con.connection.send(
|
||||
nbxmpp.Iq('set', Namespace.ROSTER, payload=[
|
||||
nbxmpp.Node('item', {'jid': jid, 'subscription': 'remove'})]))
|
||||
|
||||
def get_groups(self, jid):
|
||||
"""
|
||||
Return groups list that contact 'jid' belongs to
|
||||
"""
|
||||
return self._get_item_data(jid, 'groups')
|
||||
|
||||
def get_name(self, jid):
|
||||
"""
|
||||
Return name of contact 'jid'
|
||||
"""
|
||||
return self._get_item_data(jid, 'name')
|
||||
|
||||
def update_contact(self, jid, name, groups):
|
||||
if app.account_is_available(self._account):
|
||||
self.set_item(jid=jid, name=name, groups=groups)
|
||||
|
||||
def update_contacts(self, contacts):
|
||||
"""
|
||||
Update multiple roster items
|
||||
"""
|
||||
if app.account_is_available(self._account):
|
||||
self.set_item_multi(contacts)
|
||||
|
||||
def set_item(self, jid, name=None, groups=None):
|
||||
"""
|
||||
Rename contact 'jid' and sets the groups list that it now belongs to
|
||||
"""
|
||||
iq = nbxmpp.Iq('set', Namespace.ROSTER)
|
||||
query = iq.getTag('query')
|
||||
attrs = {'jid': jid}
|
||||
if name:
|
||||
attrs['name'] = name
|
||||
item = query.setTag('item', attrs)
|
||||
if groups is not None:
|
||||
for group in groups:
|
||||
item.addChild(node=nbxmpp.Node('group', payload=[group]))
|
||||
self._con.connection.send(iq)
|
||||
|
||||
def set_item_multi(self, items):
|
||||
"""
|
||||
Rename multiple contacts and sets their group lists
|
||||
"""
|
||||
for i in items:
|
||||
iq = nbxmpp.Iq('set', Namespace.ROSTER)
|
||||
query = iq.getTag('query')
|
||||
attrs = {'jid': i['jid']}
|
||||
if i['name']:
|
||||
attrs['name'] = i['name']
|
||||
item = query.setTag('item', attrs)
|
||||
for group in i['groups']:
|
||||
item.addChild(node=nbxmpp.Node('group', payload=[group]))
|
||||
self._con.connection.send(iq)
|
||||
|
||||
def get_items(self):
|
||||
"""
|
||||
Return list of all [bare] JIDs that the roster is currently tracks
|
||||
"""
|
||||
return list(self._data.keys())
|
||||
|
||||
def keys(self):
|
||||
"""
|
||||
Same as get_items. Provided for the sake of dictionary interface
|
||||
"""
|
||||
return list(self._data.keys())
|
||||
|
||||
def __getitem__(self, item):
|
||||
"""
|
||||
Get the contact in the internal format.
|
||||
Raises KeyError if JID 'item' is not in roster
|
||||
"""
|
||||
return self._data[item]
|
||||
|
||||
def get_item(self, item):
|
||||
"""
|
||||
Get the contact in the internal format (or None if JID 'item' is not in
|
||||
roster)
|
||||
"""
|
||||
if item in self._data:
|
||||
return self._data[item]
|
||||
return None
|
||||
|
||||
def unsubscribe(self, jid):
|
||||
"""
|
||||
Ask for removing our subscription for JID 'jid'
|
||||
"""
|
||||
self._con.connection.send(nbxmpp.Presence(jid, 'unsubscribe'))
|
||||
|
||||
def get_raw(self):
|
||||
"""
|
||||
Return the internal data representation of the roster
|
||||
"""
|
||||
return self._data
|
||||
|
||||
def set_raw(self, data):
|
||||
"""
|
||||
Set the internal data representation of the roster
|
||||
"""
|
||||
own_jid = self._con.get_own_jid().bare
|
||||
self._data = data
|
||||
self._data[own_jid] = {
|
||||
'resources': {},
|
||||
'name': None,
|
||||
'ask': None,
|
||||
'subscription': None,
|
||||
'groups': None,
|
||||
'avatar_sha': None
|
||||
}
|
||||
|
||||
def set_avatar_sha(self, jid, sha):
|
||||
if jid not in self._data:
|
||||
return
|
||||
|
||||
self._data[jid]['avatar_sha'] = sha
|
||||
self._store_roster()
|
||||
|
||||
|
||||
def get_instance(*args, **kwargs):
|
||||
return Roster(*args, **kwargs), 'Roster'
|
||||
Loading…
Add table
Add a link
Reference in a new issue