plugins XEP-0060, XEP-0277, groupblog: make use of RSM (XEP-0059)

This commit is contained in:
souliane 2014-12-15 14:04:19 +01:00
commit f3b7bd1ec2
3 changed files with 289 additions and 143 deletions

View file

@ -26,6 +26,7 @@ from twisted.words.protocols.jabber import jid
from twisted.words.xish.domish import Element, generateElementsNamed from twisted.words.xish.domish import Element, generateElementsNamed
from sat.core import exceptions from sat.core import exceptions
from wokkel import disco, data_form, iwokkel from wokkel import disco, data_form, iwokkel
from wokkel import rsm as wokkel_rsm
from zope.interface import implements from zope.interface import implements
from feed import date from feed import date
import uuid import uuid
@ -52,12 +53,17 @@ ACCESS_TYPE_MAP = { 'PUBLIC': 'open',
'JID': None, #JID is not yet managed 'JID': None, #JID is not yet managed
} }
MAX_ITEMS = 5
MAX_COMMENTS = 5
DO_NOT_COUNT_COMMENTS = -1 # must be lower than 0
PLUGIN_INFO = { PLUGIN_INFO = {
"name": "Group blogging throught collections", "name": "Group blogging throught collections",
"import_name": "GROUPBLOG", "import_name": "GROUPBLOG",
"type": "MISC", "type": "MISC",
"protocols": [], "protocols": [],
"dependencies": ["XEP-0277"], "dependencies": ["XEP-0277"],
"recommendations": ["XEP-0059"],
"main": "GroupBlog", "main": "GroupBlog",
"handler": "yes", "handler": "yes",
"description": _("""Implementation of microblogging with roster access""") "description": _("""Implementation of microblogging with roster access""")
@ -79,7 +85,6 @@ class BadAccessListError(Exception):
class UnknownType(Exception): class UnknownType(Exception):
pass pass
class GroupBlog(object): class GroupBlog(object):
"""This class use a SàT PubSub Service to manage access on microblog""" """This class use a SàT PubSub Service to manage access on microblog"""
@ -104,32 +109,27 @@ class GroupBlog(object):
async=True) async=True)
host.bridge.addMethod("getGroupBlogs", ".plugin", host.bridge.addMethod("getGroupBlogs", ".plugin",
in_sign='sass', out_sign='aa{ss}', in_sign='sasa{ss}bs', out_sign='(aa{ss}a{ss})',
method=self.getGroupBlogs, method=self.getGroupBlogs,
async=True) async=True)
host.bridge.addMethod("getGroupBlogsWithComments", ".plugin", host.bridge.addMethod("getGroupBlogsWithComments", ".plugin",
in_sign='sass', out_sign='a(a{ss}aa{ss})', in_sign='sasa{ss}is', out_sign='(a(a{ss}(aa{ss}a{ss}))a{ss})',
method=self.getGroupBlogsWithComments, method=self.getGroupBlogsWithComments,
async=True) async=True)
host.bridge.addMethod("getLastGroupBlogs", ".plugin", host.bridge.addMethod("getGroupBlogsAtom", ".plugin",
in_sign='sis', out_sign='aa{ss}', in_sign='sa{ss}s', out_sign='s',
method=self.getLastGroupBlogs, method=self.getGroupBlogsAtom,
async=True) async=True)
host.bridge.addMethod("getLastGroupBlogsAtom", ".plugin", host.bridge.addMethod("getMassiveGroupBlogs", ".plugin",
in_sign='sis', out_sign='s', in_sign='sasa{ss}s', out_sign='a{s(aa{ss}a{ss})}',
method=self.getLastGroupBlogsAtom, method=self._getMassiveGroupBlogs,
async=True)
host.bridge.addMethod("getMassiveLastGroupBlogs", ".plugin",
in_sign='sasis', out_sign='a{saa{ss}}',
method=self._getMassiveLastGroupBlogs,
async=True) async=True)
host.bridge.addMethod("getGroupBlogComments", ".plugin", host.bridge.addMethod("getGroupBlogComments", ".plugin",
in_sign='sss', out_sign='aa{ss}', in_sign='ssa{ss}s', out_sign='(aa{ss}a{ss})',
method=self.getGroupBlogComments, method=self.getGroupBlogComments,
async=True) async=True)
@ -515,78 +515,108 @@ class GroupBlog(object):
d_list.append(self.item2gbdata(item).addCallback(cb)) d_list.append(self.item2gbdata(item).addCallback(cb))
return defer.DeferredList(d_list, consumeErrors=True).addCallback(lambda result: [value for (success, value) in result if success]) return defer.DeferredList(d_list, consumeErrors=True).addCallback(lambda result: [value for (success, value) in result if success])
def __getGroupBlogs(self, pub_jid_s, max_items=10, item_ids=None, profile_key=C.PROF_KEY_NONE): def _getOrCountComments(self, items, max=0, profile_key=C.PROF_KEY_NONE):
"""Get and/or count the comments of the given items.
@param items (list): items to consider.
@param max (int): maximum number of comments to get, if 0 only count
them. The count is set to the item data of key "comments_count".
@param profile_key (str): %(doc_profile_key)s
@return: a deferred list of:
- if max == 0: microblog data
- else: couple (dict, (list[dict], dict)) containing:
- microblog data (main item)
- couple (comments data, RSM response data for the comments)
"""
def comments_cb(comments_data, entry):
entry['comments_count'] = comments_data[1]['count']
return (entry, comments_data) if max > 0 else entry
assert(max >= 0)
d_list = []
for entry in items:
if entry.get('comments', False):
comments_rsm = {'max': max}
d = self.getGroupBlogComments(entry['comments_service'], entry['comments_node'], rsm=comments_rsm, profile_key=profile_key)
d.addCallback(comments_cb, entry)
d_list.append(d)
else:
if max > 0:
d_list.append(defer.succeed((entry, ([], {}))))
else:
d_list.append(defer.succeed(entry))
deferred_list = defer.DeferredList(d_list)
deferred_list.addCallback(lambda result: [value for (success, value) in result if success])
return deferred_list
def __getGroupBlogs(self, pub_jid_s, item_ids=None, rsm=None, max_comments=0, profile_key=C.PROF_KEY_NONE):
"""Retrieve previously published items from a publish subscribe node. """Retrieve previously published items from a publish subscribe node.
@param pub_jid_s: jid of the publisher @param pub_jid_s: jid of the publisher
@param max_items: how many microblogs we want to get (see XEP-0060 #6.5.7)
@param item_ids: list of microblogs items IDs @param item_ids: list of microblogs items IDs
@param profile_key: profile key @param rsm (dict): RSM request data
@return: list of microblog data (dict) @param max_comments (int): maximum number of comments to retrieve
@param profile_key (str): %(doc_profile_key)s
@return: a deferred couple (list, dict) containing:
- list of:
- if max_comments == 0: microblog data
- else: couple (dict, (list[dict], dict)) containing:
- microblog data (main item)
- couple (comments data, RSM response data for the comments)
- RSM response data
""" """
pub_jid = jid.JID(pub_jid_s) pub_jid = jid.JID(pub_jid_s)
def initialised(result): def cb(items, client):
profile, client = result d = self._itemsConstruction(items, pub_jid, client)
d = self.host.plugins["XEP-0060"].getItems(client.item_access_pubsub, self.getNodeName(pub_jid), if max_comments == DO_NOT_COUNT_COMMENTS:
max_items=max_items, item_ids=item_ids, profile_key=profile_key) return d
d.addCallback(self._itemsConstruction, pub_jid, client) return d.addCallback(self._getOrCountComments, max_comments, profile_key)
d.addErrback(lambda ignore: {}) # TODO: more complete error management (log !)
return d
#TODO: we need to use the server corresponding the the host of the jid return DeferredItems(self, cb, None, profile_key).get(self.getNodeName(pub_jid), item_ids, rsm=rsm)
return self._initialise(profile_key).addCallback(initialised)
def getGroupBlogs(self, pub_jid_s, item_ids=None, profile_key=C.PROF_KEY_NONE): def getGroupBlogs(self, pub_jid_s, item_ids=None, rsm=None, count_comments=True, profile_key=C.PROF_KEY_NONE):
"""Get the published microblogs of the specified IDs. If item_ids is """Get the published microblogs of the specified IDs. If item_ids is
None, the result would be the same than calling getLastGroupBlogs None, the result would be the same than calling getGroupBlogs
with the default value for the attribute max_items. with the default value for the attribute max_items.
@param pub_jid_s: jid of the publisher @param pub_jid_s: jid of the publisher
@param item_ids: list of microblogs items IDs @param item_ids: list of microblogs items IDs
@param profile_key: profile key @param rsm (dict): RSM request data
@return: list of microblog data (dict) @param count_comments (bool): also count the comments if True
@param profile_key (str): %(doc_profile_key)s
@return: a deferred couple (list, dict) containing:
- list of microblog data
- RSM response data
""" """
return self.__getGroupBlogs(pub_jid_s, item_ids=item_ids, profile_key=profile_key) max_comments = 0 if count_comments else DO_NOT_COUNT_COMMENTS
return self.__getGroupBlogs(pub_jid_s, item_ids=item_ids, rsm=rsm, max_comments=max_comments, profile_key=profile_key)
def getGroupBlogsWithComments(self, pub_jid_s, item_ids=None, profile_key=C.PROF_KEY_NONE): def getGroupBlogsWithComments(self, pub_jid_s, item_ids=None, rsm=None, max_comments=None, profile_key=C.PROF_KEY_NONE):
"""Get the published microblogs of the specified IDs and their comments. If """Get the published microblogs of the specified IDs and their comments. If
item_ids is None, returns the last published microblogs and their comments. item_ids is None, returns the last published microblogs and their comments.
@param pub_jid_s: jid of the publisher @param pub_jid_s: jid of the publisher
@param item_ids: list of microblogs items IDs @param item_ids: list of microblogs items IDs
@param profile_key: profile key @param rsm (dict): RSM request data
@return: list of couple (microblog data, list of microblog data) @param max_comments (int): maximum number of comments to retrieve
@param profile_key (str): %(doc_profile_key)s
@return: a deferred couple (list, dict) containing:
- list of couple (dict, (list[dict], dict)) containing:
- microblog data (main item)
- couple (comments data, RSM response data for the comments)
- RSM response data
""" """
def get_comments(data): if max_comments is None:
d_list = [] max_comments = MAX_COMMENTS
for entry in data: assert(max_comments > 0) # otherwise the return signature is not the same
if entry.get('comments', False): return self.__getGroupBlogs(pub_jid_s, item_ids=item_ids, rsm=rsm, max_comments=max_comments, profile_key=profile_key)
d = self.getGroupBlogComments(entry['comments_service'], entry['comments_node'], profile_key=profile_key)
d.addCallback(lambda data: (entry, data))
d_list.append(d)
else:
d_list.append(defer.succeed((entry, [])))
deferred_list = defer.DeferredList(d_list)
deferred_list.addCallback(lambda result: [value for (success, value) in result if success])
return deferred_list
d = self.__getGroupBlogs(pub_jid_s, item_ids=item_ids, profile_key=profile_key) def getGroupBlogsAtom(self, pub_jid_s, rsm=None, profile_key=C.PROF_KEY_NONE):
d.addCallback(get_comments)
return d
def getLastGroupBlogs(self, pub_jid_s, max_items=10, profile_key=C.PROF_KEY_NONE):
"""Get the last published microblogs
@param pub_jid_s: jid of the publisher
@param max_items: how many microblogs we want to get (see XEP-0060 #6.5.7)
@param profile_key: profile key
@return: list of microblog data (dict)
"""
return self.__getGroupBlogs(pub_jid_s, max_items=max_items, profile_key=profile_key)
def getLastGroupBlogsAtom(self, pub_jid_s, max_items=10, profile_key=C.PROF_KEY_NONE):
"""Get the atom feed of the last published microblogs """Get the atom feed of the last published microblogs
@param pub_jid: jid of the publisher @param pub_jid: jid of the publisher
@param max_items: how many microblogs we want to get (see XEP-0060 #6.5.7)
@param profile_key: profile key @param profile_key: profile key
@return: atom XML feed (unicode) @return: a deferred unicode (atom XML feed)
""" """
pub_jid = jid.JID(pub_jid_s) pub_jid = jid.JID(pub_jid_s)
@ -617,59 +647,41 @@ class GroupBlog(object):
feed += " " + entry.toXml() + "\n" feed += " " + entry.toXml() + "\n"
return feed + "</feed>" return feed + "</feed>"
def initialised(result): def cb(items, client):
profile, client = result return items2feed(items, pub_jid, client)
d = self.host.plugins["XEP-0060"].getItems(client.item_access_pubsub, self.getNodeName(pub_jid),
max_items=max_items, profile_key=profile_key)
d.addCallback(items2feed, pub_jid, client)
d.addErrback(lambda ignore: '') # TODO: more complete error management (log !)
return d
#TODO: we need to use the server corresponding the the host of the jid d = DeferredItems(self, cb, lambda dummy: '', profile_key).get(self.getNodeName(pub_jid), rsm=rsm)
return self._initialise(profile_key).addCallback(initialised) return d.addCallback(lambda res: res[0])
def getGroupBlogComments(self, service_s, node, profile_key=C.PROF_KEY_NONE): def getGroupBlogComments(self, service_s, node, rsm=None, profile_key=C.PROF_KEY_NONE):
"""Get all comments of given node """Get all comments of given node
@param service_s: service hosting the node @param service_s: service hosting the node
@param node: comments node @param node: comments node
@param profile_key: profile key @param profile_key: profile key
@return: list of microblog data (dict) @return: a deferred couple (list, dict) containing:
- list of microblog data
- RSM response data
""" """
service = jid.JID(service_s) service = jid.JID(service_s)
def initialised(result): def cb(items, client):
profile, client = result return self._handleCommentsItems(items, service, node)
d = self.host.plugins["XEP-0060"].getItems(service, node,
profile_key=profile_key)
d.addCallback(self._handleCommentsItems, service, node)
d.addErrback(lambda ignore: {}) # TODO: more complete error management (log !)
return d
#TODO: we need to use the server corresponding the the host of the jid return DeferredItems(self, cb, None, profile_key).get(node, rsm=rsm)
return self._initialise(profile_key).addCallback(initialised)
def _getMassiveLastGroupBlogs(self, publishers_type, publishers, max_items=10, profile_key=C.PROF_KEY_NONE): def _getMassiveGroupBlogs(self, publishers_type, publishers, rsm=None, profile_key=C.PROF_KEY_NONE):
if publishers_type == 'JID': if publishers_type == 'JID':
publishers_jids = [jid.JID(publisher) for publisher in publishers] publishers_jids = [jid.JID(publisher) for publisher in publishers]
else: else:
publishers_jids = publishers publishers_jids = publishers
return self.getMassiveLastGroupBlogs(publishers_type, publishers_jids, max_items, profile_key) return self.getMassiveGroupBlogs(publishers_type, publishers_jids, rsm, profile_key)
@defer.inlineCallbacks def _getPublishersJIDs(self, publishers_type, publishers, client):
def getMassiveLastGroupBlogs(self, publishers_type, publishers, max_items=10, profile_key=C.PROF_KEY_NONE):
"""Get the last published microblogs for a list of groups or jids
@param publishers_type: type of the list of publishers (one of "GROUP" or "JID" or "ALL")
@param publishers: list of publishers, according to "publishers_type" (list of groups or list of jids)
@param max_items: how many microblogs we want to get
@param profile_key: profile key
"""
#TODO: custom exception #TODO: custom exception
if publishers_type not in ["GROUP", "JID", "ALL"]: if publishers_type not in ["GROUP", "JID", "ALL"]:
raise Exception("Bad call, unknown publishers_type") raise Exception("Bad call, unknown publishers_type")
if publishers_type == "ALL" and publishers: if publishers_type == "ALL" and publishers:
raise Exception("Publishers list must be empty when getting microblogs for all contacts") raise Exception("Publishers list must be empty when getting microblogs for all contacts")
profile, client = yield self._initialise(profile_key)
#TODO: we need to use the server corresponding the the host of the jid
if publishers_type == "ALL": if publishers_type == "ALL":
contacts = client.roster.getItems() contacts = client.roster.getItems()
@ -682,20 +694,26 @@ class GroupBlog(object):
jids = publishers jids = publishers
else: else:
raise UnknownType raise UnknownType
return jids
data = {publisher: self.getNodeName(publisher) for publisher in jids} def getMassiveGroupBlogs(self, publishers_type, publishers, rsm=None, profile_key=C.PROF_KEY_NONE):
d_dict = yield self.host.plugins["XEP-0060"].getItemsFromMany(client.item_access_pubsub, data, max_items=max_items, profile_key=profile) """Get the last published microblogs for a list of groups or jids
@param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL")
@param publishers (list): list of publishers, according to publishers_type (list of groups or list of jids)
@param rsm (dict): RSM request data, common to all publishers
@param profile_key: profile key
@return: a deferred dict with:
- key: publisher (unicode)
- value: couple (list[dict], dict) with:
- the microblogs data
- RSM response data
"""
def cb(items, publisher, client):
d = self._itemsConstruction(items, publisher, client)
return d.addCallback(self._getOrCountComments, False, profile_key)
def cb(jid): #TODO: we need to use the server corresponding to the host of the jid
def res(gbdata): return DeferredItemsFromMany(self, cb, profile_key).get(publishers_type, publishers, rsm=rsm)
return (jid.full(), gbdata)
return res
for publisher, d in d_dict.items():
d.addCallback(self._itemsConstruction, publisher, client)
d.addCallback(cb(publisher))
result = yield defer.DeferredList(d_dict.values(), consumeErrors=False)
defer.returnValue({value[0]: value[1] for success, value in result if success})
def subscribeGroupBlog(self, pub_jid, profile_key=C.PROF_KEY_NONE): def subscribeGroupBlog(self, pub_jid, profile_key=C.PROF_KEY_NONE):
def initialised(result): def initialised(result):
@ -721,30 +739,14 @@ class GroupBlog(object):
@param publishers: list of publishers, according to "publishers_type" (list of groups or list of jids) @param publishers: list of publishers, according to "publishers_type" (list of groups or list of jids)
@param profile_key: profile key @param profile_key: profile key
""" """
#TODO: custom exception
if publishers_type not in ["GROUP", "JID", "ALL"]:
raise Exception("Bad call, unknown publishers_type")
if publishers_type == "ALL" and publishers:
raise Exception("Publishers list must be empty when getting microblogs for all contacts")
profile, client = yield self._initialise(profile_key) profile, client = yield self._initialise(profile_key)
#TODO: we need to use the server corresponding the the host of the jid #TODO: we need to use the server corresponding the the host of the jid
if publishers_type == "ALL": jids = self._getPublishersJIDs(publishers_type, publishers, client)
contacts = client.roster.getItems()
jids = [contact.jid.userhostJID() for contact in contacts]
elif publishers_type == "GROUP":
jids = []
for _group in publishers:
jids.extend(client.roster.getJidsFromGroup(_group))
elif publishers_type == 'JID':
jids = publishers
else:
raise UnknownType
node_ids = [self.getNodeName(publisher) for publisher in jids] node_ids = [self.getNodeName(publisher) for publisher in jids]
d_list = yield self.host.plugins["XEP-0060"].subscribeToMany(client.item_access_pubsub, node_ids, profile_key=profile_key) d_list = yield self.host.plugins["XEP-0060"].subscribeToMany(client.item_access_pubsub, node_ids, profile_key=profile_key)
result = yield defer.DeferredList(d_list, consumeErrors=False) result = yield defer.DeferredList(d_list, consumeErrors=False)
defer.returnValue(result) defer.returnValue(None)
def deleteAllGroupBlogsAndComments(self, profile_key=C.PROF_KEY_NONE): def deleteAllGroupBlogsAndComments(self, profile_key=C.PROF_KEY_NONE):
"""Delete absolutely all the microblog data that the user has posted""" """Delete absolutely all the microblog data that the user has posted"""
@ -752,18 +754,26 @@ class GroupBlog(object):
return defer.DeferredList(calls) return defer.DeferredList(calls)
def deleteAllGroupBlogs(self, profile_key=C.PROF_KEY_NONE): def deleteAllGroupBlogs(self, profile_key=C.PROF_KEY_NONE):
"""Delete all the main items and their comments that the user has posted """Delete all the main items that the user has posted and their comments.
""" """
def initialised(result): def initialised(result):
profile, client = result profile, client = result
service = client.item_access_pubsub service = client.item_access_pubsub
jid_ = client.jid jid_ = client.jid
main_node = self.getNodeName(jid_) main_node = self.getNodeName(jid_)
d = self.host.plugins["XEP-0060"].deleteNode(service, main_node, profile_key=profile)
def cb(nodes):
d_list = []
for node in [node for node in nodes if node.endswith(main_node)]:
d = self.host.plugins["XEP-0060"].deleteNode(service, node, profile_key=profile)
d.addErrback(lambda failure: log.error(_("Deletion of node %(node)s failed: %(message)s") %
{'node': node, 'message': failure.getErrorMessage()}))
d_list.append(d)
return defer.DeferredList(d_list)
d = self.host.plugins["XEP-0060"].listNodes(service, profile=profile)
d.addCallback(cb)
d.addCallback(lambda dummy: log.info(_("All microblog's main items from %s have been deleted!") % jid_.userhost())) d.addCallback(lambda dummy: log.info(_("All microblog's main items from %s have been deleted!") % jid_.userhost()))
d.addErrback(lambda failure: log.error(_("Deletion of node %(node)s failed: %(message)s") %
{'node': main_node, 'message': failure.getErrorMessage()}))
return d return d
return self._initialise(profile_key).addCallback(initialised) return self._initialise(profile_key).addCallback(initialised)
@ -782,9 +792,11 @@ class GroupBlog(object):
jids = [contact.jid.userhostJID() for contact in client.roster.getItems()] jids = [contact.jid.userhostJID() for contact in client.roster.getItems()]
blogs = [] blogs = []
for jid_ in jids: for jid_ in jids:
if jid_ == client.jid.userhostJID():
continue # do not remove the comments on our own node
main_node = self.getNodeName(jid_) main_node = self.getNodeName(jid_)
d = self.host.plugins["XEP-0060"].getItems(service, main_node, profile_key=profile) d = self.host.plugins["XEP-0060"].getItems(service, main_node, profile_key=profile)
d.addCallback(getComments, client) d.addCallback(lambda res: getComments(res[0], client))
d.addErrback(lambda failure, main_node: log.error(_("Retrieval of items for node %(node)s failed: %(message)s") % d.addErrback(lambda failure, main_node: log.error(_("Retrieval of items for node %(node)s failed: %(message)s") %
{'node': main_node, 'message': failure.getErrorMessage()}), main_node) {'node': main_node, 'message': failure.getErrorMessage()}), main_node)
blogs.append(d) blogs.append(d)
@ -807,7 +819,7 @@ class GroupBlog(object):
href = link.getAttribute('href') href = link.getAttribute('href')
service, node = self.host.plugins['XEP-0277'].parseCommentUrl(href) service, node = self.host.plugins['XEP-0277'].parseCommentUrl(href)
d = self.host.plugins["XEP-0060"].getItems(service, node, profile_key=profile_key) d = self.host.plugins["XEP-0060"].getItems(service, node, profile_key=profile_key)
d.addCallback(lambda items, service, node: (service, node, items), service, node) d.addCallback(lambda items: (service, node, items[0]))
d.addErrback(lambda failure, node: log.error(_("Retrieval of comments for node %(node)s failed: %(message)s") % d.addErrback(lambda failure, node: log.error(_("Retrieval of comments for node %(node)s failed: %(message)s") %
{'node': node, 'message': failure.getErrorMessage()}), node) {'node': node, 'message': failure.getErrorMessage()}), node)
comments.append(d) comments.append(d)
@ -850,6 +862,116 @@ class GroupBlog(object):
return self._initialise(profile_key).addCallback(initialised) return self._initialise(profile_key).addCallback(initialised)
class DeferredItems():
"""Helper class to retrieve items using XEP-0060"""
def __init__(self, parent, cb, eb=None, profile_key=C.PROF_KEY_NONE):
"""
@param parent (GroupBlog): GroupBlog instance
@param cb (callable): callback method to be applied on items
@param eb (callable): errback method to be applied on items
@param profile_key (str): %(doc_profile_key)s
"""
self.parent = parent
self.cb = cb
self.eb = (lambda dummy: []) if eb is None else eb
self.profile_key = profile_key
def get(self, node, item_ids=None, sub_id=None, rsm=None):
"""
@param node (str): node identifier.
@param item_ids (list[str]): list of items identifiers.
@param sub_id (str): optional subscription identifier.
@param rsm (dict): RSM request data
@return: a deferred couple (list, dict) containing:
- list of microblog data
- RSM response data
"""
if rsm is None:
rsm = {'max': (len(item_ids) if item_ids else MAX_ITEMS)}
def initialised(result):
profile, client = result
rsm_ = wokkel_rsm.RSMRequest(**rsm)
d = self.parent.host.plugins["XEP-0060"].getItems(client.item_access_pubsub,
node, rsm_.max,
item_ids, sub_id, rsm_,
profile_key=profile)
def cb(result):
d = defer.maybeDeferred(self.cb, result[0], client)
return d.addCallback(lambda items: (items, result[1]))
d.addCallbacks(cb, self.eb)
return d
#TODO: we need to use the server corresponding to the host of the jid
return self.parent._initialise(self.profile_key).addCallback(initialised)
class DeferredItemsFromMany():
def __init__(self, parent, cb, profile_key=C.PROF_KEY_NONE):
"""
@param parent (GroupBlog): GroupBlog instance
@param cb (callable): callback method to be applied on items
@param profile_key (str): %(doc_profile_key)s
"""
self.parent = parent
self.cb = cb
self.profile_key = profile_key
def __buildData(self, publishers_type, publishers, client):
jids = self.parent._getPublishersJIDs(publishers_type, publishers, client)
return {publisher: self.parent.getNodeName(publisher) for publisher in jids}
def get(self, publishers_type, publishers, sub_id=None, rsm=None):
"""
@param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL")
@param publishers (list): list of publishers, according to publishers_type (list of groups or list of jids)
@param sub_id (str): optional subscription identifier.
@param rsm (dict): RSM request data
@return: a deferred dict with:
- key: publisher (unicode)
- value: couple (list[dict], dict) with:
- the microblogs data
- RSM response data
"""
if rsm is None:
rsm = {'max': MAX_ITEMS}
def initialised(result):
profile, client = result
data = self.__buildData(publishers_type, publishers, client)
rsm_ = wokkel_rsm.RSMRequest(**rsm)
d = self.parent.host.plugins["XEP-0060"].getItemsFromMany(client.item_access_pubsub,
data, rsm_.max, sub_id,
rsm_, profile_key=profile)
def cb(publisher):
def callback(result):
d = defer.maybeDeferred(self.cb, result[0], publisher, client)
d.addCallback(lambda items: (publisher.full(), (items, result[1])))
return d
return callback
def cb_list(result):
return {value[0]: value[1] for success, value in result if success}
def main_cb(result):
d_list = []
for publisher, d_items in result.items():
# XXX: trick needed as publisher is a loop variable
d_list.append(d_items.addCallback(cb(publisher)))
return defer.DeferredList(d_list, consumeErrors=False).addCallback(cb_list)
d.addCallback(main_cb)
return d
#TODO: we need to use the server corresponding to the host of the jid
return self.parent._initialise(self.profile_key).addCallback(initialised)
class GroupBlog_handler(XMPPHandler): class GroupBlog_handler(XMPPHandler):
implements(iwokkel.IDisco) implements(iwokkel.IDisco)

View file

@ -26,6 +26,7 @@ from sat.memory.memory import Sessions
from wokkel import disco, pubsub, rsm from wokkel import disco, pubsub, rsm
from zope.interface import implements from zope.interface import implements
from twisted.internet import defer from twisted.internet import defer
import uuid
PLUGIN_INFO = { PLUGIN_INFO = {
@ -154,21 +155,41 @@ class XEP_0060(object):
profile, client = self.__getClientNProfile(profile_key, 'publish item') profile, client = self.__getClientNProfile(profile_key, 'publish item')
return client.publish(service, nodeIdentifier, items, client.parent.jid) return client.publish(service, nodeIdentifier, items, client.parent.jid)
def getItems(self, service, node, max_items=None, item_ids=None, sub_id=None, profile_key=C.PROF_KEY_NONE): def getItems(self, service, node, max_items=None, item_ids=None, sub_id=None, rsm=None, profile_key=C.PROF_KEY_NONE):
"""Retrieve pubsub items from a node.
@param service (JID): target service.
@param node (str): node id.
@param max_items (int): optional limit on the number of retrieved items.
@param item_ids (list[str]): identifiers of the items to be retrieved (should not be used).
@param sub_id (str): optional subscription identifier.
@param rsm (dict): RSM request data
@param profile_key (str): %(doc_profile_key)s
@return: a deferred couple (list[dict], dict) containing:
- list of items
- RSM response data
"""
profile, client = self.__getClientNProfile(profile_key, 'get items') profile, client = self.__getClientNProfile(profile_key, 'get items')
return client.items(service, node, max_items, item_ids, sub_id, client.parent.jid) ext_data = {'id': unicode(uuid.uuid4()), 'rsm': rsm} if rsm else None
d = client.items(service, node, max_items, item_ids, sub_id, client.parent.jid, ext_data)
d.addCallback(lambda items: (items, client.getRSMResponse(ext_data['id']) if rsm else {}))
return d
@defer.inlineCallbacks @defer.inlineCallbacks
def getItemsFromMany(self, service, data, max_items=None, item_ids=None, sub_id=None, profile_key=C.PROF_KEY_NONE): def getItemsFromMany(self, service, data, max_items=None, sub_id=None, rsm=None, profile_key=C.PROF_KEY_NONE):
"""Massively retrieve pubsub items from many nodes. """Massively retrieve pubsub items from many nodes.
@param service (JID): target service. @param service (JID): target service.
@param data (dict): dictionnary binding some arbitrary keys to the node identifiers. @param data (dict): dictionnary binding some arbitrary keys to the node identifiers.
@param max_items (int): optional limit on the number of retrieved items *per node*. @param max_items (int): optional limit on the number of retrieved items *per node*.
@param item_ids (list[str]): identifiers of the items to be retrieved (should not be used).
@param sub_id (str): optional subscription identifier. @param sub_id (str): optional subscription identifier.
@param rsm (dict): RSM request data
@param profile_key (str): %(doc_profile_key)s @param profile_key (str): %(doc_profile_key)s
@return: dict binding a subset of the keys of data to Deferred instances. @return: a deferred dict with:
- key: a value in (a subset of) data.keys()
- couple (list[dict], dict) containing:
- list of items
- RSM response data
""" """
profile, client = self.__getClientNProfile(profile_key, 'get items') profile, client = self.__getClientNProfile(profile_key, 'get items')
found_nodes = yield self.listNodes(service, profile=profile) found_nodes = yield self.listNodes(service, profile=profile)
@ -177,7 +198,7 @@ class XEP_0060(object):
if node not in found_nodes: if node not in found_nodes:
log.debug("Skip the items retrieval for [{node}]: node doesn't exist".format(node=node)) log.debug("Skip the items retrieval for [{node}]: node doesn't exist".format(node=node))
continue # avoid pubsub "item-not-found" error continue # avoid pubsub "item-not-found" error
d_dict[publisher] = client.items(service, node, max_items, item_ids, sub_id, client.parent.jid) d_dict[publisher] = self.getItems(service, node, max_items, None, sub_id, rsm, profile)
defer.returnValue(d_dict) defer.returnValue(d_dict)
def getOptions(self, service, nodeIdentifier, subscriber, subscriptionIdentifier=None, profile_key=C.PROF_KEY_NONE): def getOptions(self, service, nodeIdentifier, subscriber, subscriptionIdentifier=None, profile_key=C.PROF_KEY_NONE):

View file

@ -44,6 +44,7 @@ PLUGIN_INFO = {
"type": "XEP", "type": "XEP",
"protocols": [], "protocols": [],
"dependencies": ["XEP-0163", "XEP-0060", "TEXT-SYNTAXES"], "dependencies": ["XEP-0163", "XEP-0060", "TEXT-SYNTAXES"],
"recommendations": ["XEP-0059"],
"main": "XEP_0277", "main": "XEP_0277",
"handler": "no", "handler": "no",
"description": _("""Implementation of microblogging Protocol""") "description": _("""Implementation of microblogging Protocol""")
@ -77,8 +78,8 @@ class XEP_0277(object):
def parseCommentUrl(self, node_url): def parseCommentUrl(self, node_url):
"""Determine the fields comments_service and comments_node of a microblog data """Determine the fields comments_service and comments_node of a microblog data
from the href attribute of an entry's link element. For example this input: from the href attribute of an entry's link element. For example this input:
xmpp:sat-pubsub.libervia.org?node=urn%3Axmpp%3Acomments%3A_c5c4a142-2279-4b2a-ba4c-1bc33aa87634__urn%3Axmpp%3Agroupblog%3Asouliane%libervia.org xmpp:sat-pubsub.libervia.org?node=urn%3Axmpp%3Acomments%3A_c5c4a142-2279-4b2a-ba4c-1bc33aa87634__urn%3Axmpp%3Agroupblog%3Asouliane%40libervia.org
will return (JID(u'sat-pubsub.libervia.org'), 'urn:xmpp:comments:_c5c4a142-2279-4b2a-ba4c-1bc33aa87634__urn:xmpp:groupblog:souliane%libervia.org') will return (JID(u'sat-pubsub.libervia.org'), 'urn:xmpp:comments:_c5c4a142-2279-4b2a-ba4c-1bc33aa87634__urn:xmpp:groupblog:souliane@libervia.org')
@return: a tuple (JID, str) @return: a tuple (JID, str)
""" """
parsed_url = urlparse.urlparse(node_url, 'xmpp') parsed_url = urlparse.urlparse(node_url, 'xmpp')
@ -315,10 +316,12 @@ class XEP_0277(object):
@param pub_jid: jid of the publisher @param pub_jid: jid of the publisher
@param max_items: how many microblogs we want to get @param max_items: how many microblogs we want to get
@param profile_key: profile key @param profile_key: profile key
@return: a deferred couple with the list of items and RSM information.
""" """
d = self.host.plugins["XEP-0060"].getItems(jid.JID(pub_jid), NS_MICROBLOG, max_items=max_items, profile_key=profile_key) d = self.host.plugins["XEP-0060"].getItems(jid.JID(pub_jid), NS_MICROBLOG, max_items=max_items, profile_key=profile_key)
d.addCallback(lambda items: defer.DeferredList(map(self.item2mbdata, items), consumeErrors=True)) d.addCallback(lambda res: (defer.DeferredList(map(self.item2mbdata, res[0]), consumeErrors=True), res[1]))
d.addCallback(lambda result: [value for (success, value) in result if success]) d.addCallback(lambda res: ([value for (success, value) in res[0] if success], res[1]))
return d return d
def setMicroblogAccess(self, access="presence", profile_key=C.PROF_KEY_NONE): def setMicroblogAccess(self, access="presence", profile_key=C.PROF_KEY_NONE):