Add support for roster versioning.
This was XEP-0237, but is now part of RFC 6121. Roster backends should now expose two additional methods: version(jid): Return the version of the given JID's roster. set_version(jid, version): Update the version of the given JID's roster. A new state field will be passed to the backend if an item has been marked for removal. This is 'removed' which will be set to True.
This commit is contained in:
parent
d41ada6b66
commit
a71823dc04
@ -18,6 +18,7 @@ import logging
|
||||
|
||||
from sleekxmpp.stanza import StreamFeatures
|
||||
from sleekxmpp.basexmpp import BaseXMPP
|
||||
from sleekxmpp.exceptions import XMPPError
|
||||
from sleekxmpp.xmlstream import XMLStream
|
||||
from sleekxmpp.xmlstream.matcher import MatchXPath
|
||||
from sleekxmpp.xmlstream.handler import Callback
|
||||
@ -111,6 +112,7 @@ class ClientXMPP(BaseXMPP):
|
||||
self.register_plugin('feature_session')
|
||||
self.register_plugin('feature_mechanisms',
|
||||
pconfig={'use_mech': sasl_mech} if sasl_mech else None)
|
||||
self.register_plugin('feature_rosterver')
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
@ -240,6 +242,8 @@ class ClientXMPP(BaseXMPP):
|
||||
iq = self.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq.enable('roster')
|
||||
if 'rosterver' in self.features:
|
||||
iq['roster']['ver'] = self.client_roster.version
|
||||
|
||||
if not block and callback is None:
|
||||
callback = lambda resp: self._handle_roster(resp, request=True)
|
||||
@ -279,15 +283,22 @@ class ClientXMPP(BaseXMPP):
|
||||
to a request for the roster, and not an
|
||||
empty acknowledgement from the server.
|
||||
"""
|
||||
if iq['from'].bare and iq['from'].bare != self.boundjid.bare:
|
||||
raise XMPPError(condition='service-unavailable')
|
||||
if iq['type'] == 'set' or (iq['type'] == 'result' and request):
|
||||
roster = self.client_roster
|
||||
if iq['roster']['ver']:
|
||||
roster.version = iq['roster']['ver']
|
||||
for jid in iq['roster']['items']:
|
||||
item = iq['roster']['items'][jid]
|
||||
roster = self.roster[iq['to'].bare]
|
||||
roster[jid]['name'] = item['name']
|
||||
roster[jid]['groups'] = item['groups']
|
||||
roster[jid]['from'] = item['subscription'] in ['from', 'both']
|
||||
roster[jid]['to'] = item['subscription'] in ['to', 'both']
|
||||
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
|
||||
|
||||
roster[jid].save(remove=(item['subscription'] == 'remove'))
|
||||
|
||||
self.event('roster_received', iq)
|
||||
|
||||
self.event("roster_update", iq)
|
||||
|
@ -6,4 +6,4 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
__all__ = ['feature_starttls', 'feature_mechanisms', 'feature_bind']
|
||||
__all__ = ['feature_starttls', 'feature_mechanisms', 'feature_bind', 'feature_rosterver']
|
||||
|
10
sleekxmpp/features/feature_rosterver/__init__.py
Normal file
10
sleekxmpp/features/feature_rosterver/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp.features.feature_rosterver.rosterver import feature_rosterver
|
||||
from sleekxmpp.features.feature_rosterver.stanza import RosterVer
|
42
sleekxmpp/features/feature_rosterver/rosterver.py
Normal file
42
sleekxmpp/features/feature_rosterver/rosterver.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from sleekxmpp.stanza import Iq, StreamFeatures
|
||||
from sleekxmpp.features.feature_rosterver import stanza
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.plugins.base import base_plugin
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class feature_rosterver(base_plugin):
|
||||
|
||||
def plugin_init(self):
|
||||
self.name = 'Roster Versioning'
|
||||
self.rfc = '6121'
|
||||
self.description = 'Roster Versioning'
|
||||
self.stanza = stanza
|
||||
|
||||
self.xmpp.register_feature('rosterver',
|
||||
self._handle_rosterver,
|
||||
restart=False,
|
||||
order=9000)
|
||||
|
||||
register_stanza_plugin(StreamFeatures, stanza.RosterVer)
|
||||
|
||||
def _handle_rosterver(self, features):
|
||||
"""Enable using roster versioning.
|
||||
|
||||
Arguments:
|
||||
features -- The stream features stanza.
|
||||
"""
|
||||
log.debug("Enabling roster versioning.")
|
||||
self.xmpp.features.add('rosterver')
|
17
sleekxmpp/features/feature_rosterver/stanza.py
Normal file
17
sleekxmpp/features/feature_rosterver/stanza.py
Normal file
@ -0,0 +1,17 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp.xmlstream import ElementBase
|
||||
|
||||
|
||||
class RosterVer(ElementBase):
|
||||
|
||||
name = 'ver'
|
||||
namespace = 'urn:xmpp:features:rosterver'
|
||||
interfaces = set()
|
||||
plugin_attrib = 'rosterver'
|
@ -134,6 +134,7 @@ class RosterItem(object):
|
||||
'subscription': 'none',
|
||||
'name': '',
|
||||
'groups': []}
|
||||
|
||||
self._db_state = {}
|
||||
self.load()
|
||||
|
||||
@ -171,16 +172,25 @@ class RosterItem(object):
|
||||
return self._state
|
||||
return None
|
||||
|
||||
def save(self):
|
||||
def save(self, remove=False):
|
||||
"""
|
||||
Save the item's state information to an external datastore,
|
||||
if one has been provided.
|
||||
|
||||
Arguments:
|
||||
remove -- If True, expunge the item from the datastore.
|
||||
"""
|
||||
self['subscription'] = self._subscription()
|
||||
if remove:
|
||||
self._state['removed'] = True
|
||||
if self.db:
|
||||
self.db.save(self.owner, self.jid,
|
||||
self._state, self._db_state)
|
||||
|
||||
# Finally, remove the in-memory copy if needed.
|
||||
if remove:
|
||||
del self.xmpp.roster[self.owner][self.jid]
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Return a state field's value."""
|
||||
if key in self._state:
|
||||
|
@ -57,11 +57,28 @@ class RosterNode(object):
|
||||
self.auto_authorize = True
|
||||
self.auto_subscribe = True
|
||||
self.last_status = None
|
||||
self._version = ''
|
||||
self._jids = {}
|
||||
|
||||
if self.db:
|
||||
if hasattr(self.db, 'version'):
|
||||
self._version = self.db.version(self.jid)
|
||||
for jid in self.db.entries(self.jid):
|
||||
self.add(jid)
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""Retrieve the roster's version ID."""
|
||||
if self.db and hasattr(self.db, 'version'):
|
||||
self._version = self.db.version(self.jid)
|
||||
return self._version
|
||||
|
||||
@version.setter
|
||||
def version(self, version):
|
||||
"""Set the roster's version ID."""
|
||||
self._version = version
|
||||
if self.db and hasattr(self.db, 'set_version'):
|
||||
self.db.set_version(self.jid, version)
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""
|
||||
@ -75,6 +92,17 @@ class RosterNode(object):
|
||||
self.add(key, save=True)
|
||||
return self._jids[key]
|
||||
|
||||
def __delitem__(self, key):
|
||||
"""
|
||||
Remove a roster item from the local storage.
|
||||
|
||||
To remove an item from the server, use the remove() method.
|
||||
"""
|
||||
if isinstance(key, JID):
|
||||
key = key.bare
|
||||
if key in self._jids:
|
||||
del self._jids[key]
|
||||
|
||||
def __len__(self):
|
||||
"""Return the number of JIDs referenced by the roster."""
|
||||
return len(self._jids)
|
||||
|
@ -36,7 +36,30 @@ class Roster(ElementBase):
|
||||
namespace = 'jabber:iq:roster'
|
||||
name = 'query'
|
||||
plugin_attrib = 'roster'
|
||||
interfaces = set(('items',))
|
||||
interfaces = set(('items', 'ver'))
|
||||
|
||||
def get_ver(self):
|
||||
"""
|
||||
Ensure handling an empty ver attribute propery.
|
||||
|
||||
The ver attribute is special in that the presence of the
|
||||
attribute with an empty value is important for boostrapping
|
||||
roster versioning.
|
||||
"""
|
||||
return self.xml.attrib.get('ver', None)
|
||||
|
||||
def set_ver(self, ver):
|
||||
"""
|
||||
Ensure handling an empty ver attribute propery.
|
||||
|
||||
The ver attribute is special in that the presence of the
|
||||
attribute with an empty value is important for boostrapping
|
||||
roster versioning.
|
||||
"""
|
||||
if ver is not None:
|
||||
self.xml.attrib['ver'] = ver
|
||||
else:
|
||||
del self.xml.attrib['ver']
|
||||
|
||||
def set_items(self, items):
|
||||
"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user