comparison src/plugins/plugin_misc_groupblog.py @ 1268:bb30bf3ae932

plugins XEP-0060, XEP-0277, groupblog: make use of RSM (XEP-0059)
author souliane <souliane@mailoo.org>
date Mon, 15 Dec 2014 14:04:19 +0100
parents b4a264915ea9
children 1c90a913fbb9
comparison
equal deleted inserted replaced
1267:ea692d51a0ee 1268:bb30bf3ae932
24 from twisted.internet import defer 24 from twisted.internet import defer
25 from twisted.words.protocols.jabber import jid 25 from twisted.words.protocols.jabber import jid
26 from twisted.words.xish.domish import Element, generateElementsNamed 26 from twisted.words.xish.domish import Element, generateElementsNamed
27 from sat.core import exceptions 27 from sat.core import exceptions
28 from wokkel import disco, data_form, iwokkel 28 from wokkel import disco, data_form, iwokkel
29 from wokkel import rsm as wokkel_rsm
29 from zope.interface import implements 30 from zope.interface import implements
30 from feed import date 31 from feed import date
31 import uuid 32 import uuid
32 import urllib 33 import urllib
33 34
50 ACCESS_TYPE_MAP = { 'PUBLIC': 'open', 51 ACCESS_TYPE_MAP = { 'PUBLIC': 'open',
51 'GROUP': 'roster', 52 'GROUP': 'roster',
52 'JID': None, #JID is not yet managed 53 'JID': None, #JID is not yet managed
53 } 54 }
54 55
56 MAX_ITEMS = 5
57 MAX_COMMENTS = 5
58 DO_NOT_COUNT_COMMENTS = -1 # must be lower than 0
59
55 PLUGIN_INFO = { 60 PLUGIN_INFO = {
56 "name": "Group blogging throught collections", 61 "name": "Group blogging throught collections",
57 "import_name": "GROUPBLOG", 62 "import_name": "GROUPBLOG",
58 "type": "MISC", 63 "type": "MISC",
59 "protocols": [], 64 "protocols": [],
60 "dependencies": ["XEP-0277"], 65 "dependencies": ["XEP-0277"],
66 "recommendations": ["XEP-0059"],
61 "main": "GroupBlog", 67 "main": "GroupBlog",
62 "handler": "yes", 68 "handler": "yes",
63 "description": _("""Implementation of microblogging with roster access""") 69 "description": _("""Implementation of microblogging with roster access""")
64 } 70 }
65 71
77 83
78 84
79 class UnknownType(Exception): 85 class UnknownType(Exception):
80 pass 86 pass
81 87
82
83 class GroupBlog(object): 88 class GroupBlog(object):
84 """This class use a SàT PubSub Service to manage access on microblog""" 89 """This class use a SàT PubSub Service to manage access on microblog"""
85 90
86 def __init__(self, host): 91 def __init__(self, host):
87 log.info(_("Group blog plugin initialization")) 92 log.info(_("Group blog plugin initialization"))
102 host.bridge.addMethod("sendGroupBlogComment", ".plugin", in_sign='ssa{ss}s', out_sign='', 107 host.bridge.addMethod("sendGroupBlogComment", ".plugin", in_sign='ssa{ss}s', out_sign='',
103 method=self.sendGroupBlogComment, 108 method=self.sendGroupBlogComment,
104 async=True) 109 async=True)
105 110
106 host.bridge.addMethod("getGroupBlogs", ".plugin", 111 host.bridge.addMethod("getGroupBlogs", ".plugin",
107 in_sign='sass', out_sign='aa{ss}', 112 in_sign='sasa{ss}bs', out_sign='(aa{ss}a{ss})',
108 method=self.getGroupBlogs, 113 method=self.getGroupBlogs,
109 async=True) 114 async=True)
110 115
111 host.bridge.addMethod("getGroupBlogsWithComments", ".plugin", 116 host.bridge.addMethod("getGroupBlogsWithComments", ".plugin",
112 in_sign='sass', out_sign='a(a{ss}aa{ss})', 117 in_sign='sasa{ss}is', out_sign='(a(a{ss}(aa{ss}a{ss}))a{ss})',
113 method=self.getGroupBlogsWithComments, 118 method=self.getGroupBlogsWithComments,
114 async=True) 119 async=True)
115 120
116 host.bridge.addMethod("getLastGroupBlogs", ".plugin", 121 host.bridge.addMethod("getGroupBlogsAtom", ".plugin",
117 in_sign='sis', out_sign='aa{ss}', 122 in_sign='sa{ss}s', out_sign='s',
118 method=self.getLastGroupBlogs, 123 method=self.getGroupBlogsAtom,
119 async=True) 124 async=True)
120 125
121 host.bridge.addMethod("getLastGroupBlogsAtom", ".plugin", 126 host.bridge.addMethod("getMassiveGroupBlogs", ".plugin",
122 in_sign='sis', out_sign='s', 127 in_sign='sasa{ss}s', out_sign='a{s(aa{ss}a{ss})}',
123 method=self.getLastGroupBlogsAtom, 128 method=self._getMassiveGroupBlogs,
124 async=True)
125
126 host.bridge.addMethod("getMassiveLastGroupBlogs", ".plugin",
127 in_sign='sasis', out_sign='a{saa{ss}}',
128 method=self._getMassiveLastGroupBlogs,
129 async=True) 129 async=True)
130 130
131 host.bridge.addMethod("getGroupBlogComments", ".plugin", 131 host.bridge.addMethod("getGroupBlogComments", ".plugin",
132 in_sign='sss', out_sign='aa{ss}', 132 in_sign='ssa{ss}s', out_sign='(aa{ss}a{ss})',
133 method=self.getGroupBlogComments, 133 method=self.getGroupBlogComments,
134 async=True) 134 async=True)
135 135
136 host.bridge.addMethod("subscribeGroupBlog", ".plugin", in_sign='ss', out_sign='', 136 host.bridge.addMethod("subscribeGroupBlog", ".plugin", in_sign='ss', out_sign='',
137 method=self.subscribeGroupBlog, 137 method=self.subscribeGroupBlog,
513 513
514 for item in items: 514 for item in items:
515 d_list.append(self.item2gbdata(item).addCallback(cb)) 515 d_list.append(self.item2gbdata(item).addCallback(cb))
516 return defer.DeferredList(d_list, consumeErrors=True).addCallback(lambda result: [value for (success, value) in result if success]) 516 return defer.DeferredList(d_list, consumeErrors=True).addCallback(lambda result: [value for (success, value) in result if success])
517 517
518 def __getGroupBlogs(self, pub_jid_s, max_items=10, item_ids=None, profile_key=C.PROF_KEY_NONE): 518 def _getOrCountComments(self, items, max=0, profile_key=C.PROF_KEY_NONE):
519 """Get and/or count the comments of the given items.
520
521 @param items (list): items to consider.
522 @param max (int): maximum number of comments to get, if 0 only count
523 them. The count is set to the item data of key "comments_count".
524 @param profile_key (str): %(doc_profile_key)s
525 @return: a deferred list of:
526 - if max == 0: microblog data
527 - else: couple (dict, (list[dict], dict)) containing:
528 - microblog data (main item)
529 - couple (comments data, RSM response data for the comments)
530 """
531 def comments_cb(comments_data, entry):
532 entry['comments_count'] = comments_data[1]['count']
533 return (entry, comments_data) if max > 0 else entry
534
535 assert(max >= 0)
536 d_list = []
537 for entry in items:
538 if entry.get('comments', False):
539 comments_rsm = {'max': max}
540 d = self.getGroupBlogComments(entry['comments_service'], entry['comments_node'], rsm=comments_rsm, profile_key=profile_key)
541 d.addCallback(comments_cb, entry)
542 d_list.append(d)
543 else:
544 if max > 0:
545 d_list.append(defer.succeed((entry, ([], {}))))
546 else:
547 d_list.append(defer.succeed(entry))
548 deferred_list = defer.DeferredList(d_list)
549 deferred_list.addCallback(lambda result: [value for (success, value) in result if success])
550 return deferred_list
551
552 def __getGroupBlogs(self, pub_jid_s, item_ids=None, rsm=None, max_comments=0, profile_key=C.PROF_KEY_NONE):
519 """Retrieve previously published items from a publish subscribe node. 553 """Retrieve previously published items from a publish subscribe node.
520 @param pub_jid_s: jid of the publisher 554
521 @param max_items: how many microblogs we want to get (see XEP-0060 #6.5.7)
522 @param item_ids: list of microblogs items IDs
523 @param profile_key: profile key
524 @return: list of microblog data (dict)
525 """
526 pub_jid = jid.JID(pub_jid_s)
527
528 def initialised(result):
529 profile, client = result
530 d = self.host.plugins["XEP-0060"].getItems(client.item_access_pubsub, self.getNodeName(pub_jid),
531 max_items=max_items, item_ids=item_ids, profile_key=profile_key)
532 d.addCallback(self._itemsConstruction, pub_jid, client)
533 d.addErrback(lambda ignore: {}) # TODO: more complete error management (log !)
534 return d
535
536 #TODO: we need to use the server corresponding the the host of the jid
537 return self._initialise(profile_key).addCallback(initialised)
538
539 def getGroupBlogs(self, pub_jid_s, item_ids=None, profile_key=C.PROF_KEY_NONE):
540 """Get the published microblogs of the specified IDs. If item_ids is
541 None, the result would be the same than calling getLastGroupBlogs
542 with the default value for the attribute max_items.
543 @param pub_jid_s: jid of the publisher 555 @param pub_jid_s: jid of the publisher
544 @param item_ids: list of microblogs items IDs 556 @param item_ids: list of microblogs items IDs
545 @param profile_key: profile key 557 @param rsm (dict): RSM request data
546 @return: list of microblog data (dict) 558 @param max_comments (int): maximum number of comments to retrieve
547 """ 559 @param profile_key (str): %(doc_profile_key)s
548 return self.__getGroupBlogs(pub_jid_s, item_ids=item_ids, profile_key=profile_key) 560 @return: a deferred couple (list, dict) containing:
549 561 - list of:
550 def getGroupBlogsWithComments(self, pub_jid_s, item_ids=None, profile_key=C.PROF_KEY_NONE): 562 - if max_comments == 0: microblog data
563 - else: couple (dict, (list[dict], dict)) containing:
564 - microblog data (main item)
565 - couple (comments data, RSM response data for the comments)
566 - RSM response data
567 """
568 pub_jid = jid.JID(pub_jid_s)
569
570 def cb(items, client):
571 d = self._itemsConstruction(items, pub_jid, client)
572 if max_comments == DO_NOT_COUNT_COMMENTS:
573 return d
574 return d.addCallback(self._getOrCountComments, max_comments, profile_key)
575
576 return DeferredItems(self, cb, None, profile_key).get(self.getNodeName(pub_jid), item_ids, rsm=rsm)
577
578 def getGroupBlogs(self, pub_jid_s, item_ids=None, rsm=None, count_comments=True, profile_key=C.PROF_KEY_NONE):
579 """Get the published microblogs of the specified IDs. If item_ids is
580 None, the result would be the same than calling getGroupBlogs
581 with the default value for the attribute max_items.
582
583 @param pub_jid_s: jid of the publisher
584 @param item_ids: list of microblogs items IDs
585 @param rsm (dict): RSM request data
586 @param count_comments (bool): also count the comments if True
587 @param profile_key (str): %(doc_profile_key)s
588 @return: a deferred couple (list, dict) containing:
589 - list of microblog data
590 - RSM response data
591 """
592 max_comments = 0 if count_comments else DO_NOT_COUNT_COMMENTS
593 return self.__getGroupBlogs(pub_jid_s, item_ids=item_ids, rsm=rsm, max_comments=max_comments, profile_key=profile_key)
594
595 def getGroupBlogsWithComments(self, pub_jid_s, item_ids=None, rsm=None, max_comments=None, profile_key=C.PROF_KEY_NONE):
551 """Get the published microblogs of the specified IDs and their comments. If 596 """Get the published microblogs of the specified IDs and their comments. If
552 item_ids is None, returns the last published microblogs and their comments. 597 item_ids is None, returns the last published microblogs and their comments.
598
553 @param pub_jid_s: jid of the publisher 599 @param pub_jid_s: jid of the publisher
554 @param item_ids: list of microblogs items IDs 600 @param item_ids: list of microblogs items IDs
555 @param profile_key: profile key 601 @param rsm (dict): RSM request data
556 @return: list of couple (microblog data, list of microblog data) 602 @param max_comments (int): maximum number of comments to retrieve
557 """ 603 @param profile_key (str): %(doc_profile_key)s
558 def get_comments(data): 604 @return: a deferred couple (list, dict) containing:
559 d_list = [] 605 - list of couple (dict, (list[dict], dict)) containing:
560 for entry in data: 606 - microblog data (main item)
561 if entry.get('comments', False): 607 - couple (comments data, RSM response data for the comments)
562 d = self.getGroupBlogComments(entry['comments_service'], entry['comments_node'], profile_key=profile_key) 608 - RSM response data
563 d.addCallback(lambda data: (entry, data)) 609 """
564 d_list.append(d) 610 if max_comments is None:
565 else: 611 max_comments = MAX_COMMENTS
566 d_list.append(defer.succeed((entry, []))) 612 assert(max_comments > 0) # otherwise the return signature is not the same
567 deferred_list = defer.DeferredList(d_list) 613 return self.__getGroupBlogs(pub_jid_s, item_ids=item_ids, rsm=rsm, max_comments=max_comments, profile_key=profile_key)
568 deferred_list.addCallback(lambda result: [value for (success, value) in result if success]) 614
569 return deferred_list 615 def getGroupBlogsAtom(self, pub_jid_s, rsm=None, profile_key=C.PROF_KEY_NONE):
570
571 d = self.__getGroupBlogs(pub_jid_s, item_ids=item_ids, profile_key=profile_key)
572 d.addCallback(get_comments)
573 return d
574
575 def getLastGroupBlogs(self, pub_jid_s, max_items=10, profile_key=C.PROF_KEY_NONE):
576 """Get the last published microblogs
577 @param pub_jid_s: jid of the publisher
578 @param max_items: how many microblogs we want to get (see XEP-0060 #6.5.7)
579 @param profile_key: profile key
580 @return: list of microblog data (dict)
581 """
582 return self.__getGroupBlogs(pub_jid_s, max_items=max_items, profile_key=profile_key)
583
584 def getLastGroupBlogsAtom(self, pub_jid_s, max_items=10, profile_key=C.PROF_KEY_NONE):
585 """Get the atom feed of the last published microblogs 616 """Get the atom feed of the last published microblogs
586 @param pub_jid: jid of the publisher 617 @param pub_jid: jid of the publisher
587 @param max_items: how many microblogs we want to get (see XEP-0060 #6.5.7)
588 @param profile_key: profile key 618 @param profile_key: profile key
589 @return: atom XML feed (unicode) 619 @return: a deferred unicode (atom XML feed)
590 """ 620 """
591 pub_jid = jid.JID(pub_jid_s) 621 pub_jid = jid.JID(pub_jid_s)
592 622
593 def removeAllURIs(element): 623 def removeAllURIs(element):
594 """Recursively remove the URIs of the element and its children. 624 """Recursively remove the URIs of the element and its children.
615 entry = item.firstChildElement() 645 entry = item.firstChildElement()
616 removeAllURIs(entry) 646 removeAllURIs(entry)
617 feed += " " + entry.toXml() + "\n" 647 feed += " " + entry.toXml() + "\n"
618 return feed + "</feed>" 648 return feed + "</feed>"
619 649
620 def initialised(result): 650 def cb(items, client):
621 profile, client = result 651 return items2feed(items, pub_jid, client)
622 d = self.host.plugins["XEP-0060"].getItems(client.item_access_pubsub, self.getNodeName(pub_jid), 652
623 max_items=max_items, profile_key=profile_key) 653 d = DeferredItems(self, cb, lambda dummy: '', profile_key).get(self.getNodeName(pub_jid), rsm=rsm)
624 d.addCallback(items2feed, pub_jid, client) 654 return d.addCallback(lambda res: res[0])
625 d.addErrback(lambda ignore: '') # TODO: more complete error management (log !) 655
626 return d 656 def getGroupBlogComments(self, service_s, node, rsm=None, profile_key=C.PROF_KEY_NONE):
627
628 #TODO: we need to use the server corresponding the the host of the jid
629 return self._initialise(profile_key).addCallback(initialised)
630
631 def getGroupBlogComments(self, service_s, node, profile_key=C.PROF_KEY_NONE):
632 """Get all comments of given node 657 """Get all comments of given node
633 @param service_s: service hosting the node 658 @param service_s: service hosting the node
634 @param node: comments node 659 @param node: comments node
635 @param profile_key: profile key 660 @param profile_key: profile key
636 @return: list of microblog data (dict) 661 @return: a deferred couple (list, dict) containing:
662 - list of microblog data
663 - RSM response data
637 """ 664 """
638 service = jid.JID(service_s) 665 service = jid.JID(service_s)
639 666
640 def initialised(result): 667 def cb(items, client):
641 profile, client = result 668 return self._handleCommentsItems(items, service, node)
642 d = self.host.plugins["XEP-0060"].getItems(service, node, 669
643 profile_key=profile_key) 670 return DeferredItems(self, cb, None, profile_key).get(node, rsm=rsm)
644 d.addCallback(self._handleCommentsItems, service, node) 671
645 d.addErrback(lambda ignore: {}) # TODO: more complete error management (log !) 672 def _getMassiveGroupBlogs(self, publishers_type, publishers, rsm=None, profile_key=C.PROF_KEY_NONE):
646 return d
647
648 #TODO: we need to use the server corresponding the the host of the jid
649 return self._initialise(profile_key).addCallback(initialised)
650
651 def _getMassiveLastGroupBlogs(self, publishers_type, publishers, max_items=10, profile_key=C.PROF_KEY_NONE):
652 if publishers_type == 'JID': 673 if publishers_type == 'JID':
653 publishers_jids = [jid.JID(publisher) for publisher in publishers] 674 publishers_jids = [jid.JID(publisher) for publisher in publishers]
654 else: 675 else:
655 publishers_jids = publishers 676 publishers_jids = publishers
656 return self.getMassiveLastGroupBlogs(publishers_type, publishers_jids, max_items, profile_key) 677 return self.getMassiveGroupBlogs(publishers_type, publishers_jids, rsm, profile_key)
657 678
658 @defer.inlineCallbacks 679 def _getPublishersJIDs(self, publishers_type, publishers, client):
659 def getMassiveLastGroupBlogs(self, publishers_type, publishers, max_items=10, profile_key=C.PROF_KEY_NONE):
660 """Get the last published microblogs for a list of groups or jids
661 @param publishers_type: type of the list of publishers (one of "GROUP" or "JID" or "ALL")
662 @param publishers: list of publishers, according to "publishers_type" (list of groups or list of jids)
663 @param max_items: how many microblogs we want to get
664 @param profile_key: profile key
665 """
666 #TODO: custom exception 680 #TODO: custom exception
667 if publishers_type not in ["GROUP", "JID", "ALL"]: 681 if publishers_type not in ["GROUP", "JID", "ALL"]:
668 raise Exception("Bad call, unknown publishers_type") 682 raise Exception("Bad call, unknown publishers_type")
669 if publishers_type == "ALL" and publishers: 683 if publishers_type == "ALL" and publishers:
670 raise Exception("Publishers list must be empty when getting microblogs for all contacts") 684 raise Exception("Publishers list must be empty when getting microblogs for all contacts")
671 profile, client = yield self._initialise(profile_key)
672 #TODO: we need to use the server corresponding the the host of the jid
673 685
674 if publishers_type == "ALL": 686 if publishers_type == "ALL":
675 contacts = client.roster.getItems() 687 contacts = client.roster.getItems()
676 jids = [contact.jid.userhostJID() for contact in contacts] 688 jids = [contact.jid.userhostJID() for contact in contacts]
677 elif publishers_type == "GROUP": 689 elif publishers_type == "GROUP":
680 jids.extend(client.roster.getJidsFromGroup(_group)) 692 jids.extend(client.roster.getJidsFromGroup(_group))
681 elif publishers_type == 'JID': 693 elif publishers_type == 'JID':
682 jids = publishers 694 jids = publishers
683 else: 695 else:
684 raise UnknownType 696 raise UnknownType
685 697 return jids
686 data = {publisher: self.getNodeName(publisher) for publisher in jids} 698
687 d_dict = yield self.host.plugins["XEP-0060"].getItemsFromMany(client.item_access_pubsub, data, max_items=max_items, profile_key=profile) 699 def getMassiveGroupBlogs(self, publishers_type, publishers, rsm=None, profile_key=C.PROF_KEY_NONE):
688 700 """Get the last published microblogs for a list of groups or jids
689 def cb(jid): 701 @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL")
690 def res(gbdata): 702 @param publishers (list): list of publishers, according to publishers_type (list of groups or list of jids)
691 return (jid.full(), gbdata) 703 @param rsm (dict): RSM request data, common to all publishers
692 return res 704 @param profile_key: profile key
693 705 @return: a deferred dict with:
694 for publisher, d in d_dict.items(): 706 - key: publisher (unicode)
695 d.addCallback(self._itemsConstruction, publisher, client) 707 - value: couple (list[dict], dict) with:
696 d.addCallback(cb(publisher)) 708 - the microblogs data
697 result = yield defer.DeferredList(d_dict.values(), consumeErrors=False) 709 - RSM response data
698 defer.returnValue({value[0]: value[1] for success, value in result if success}) 710 """
711 def cb(items, publisher, client):
712 d = self._itemsConstruction(items, publisher, client)
713 return d.addCallback(self._getOrCountComments, False, profile_key)
714
715 #TODO: we need to use the server corresponding to the host of the jid
716 return DeferredItemsFromMany(self, cb, profile_key).get(publishers_type, publishers, rsm=rsm)
699 717
700 def subscribeGroupBlog(self, pub_jid, profile_key=C.PROF_KEY_NONE): 718 def subscribeGroupBlog(self, pub_jid, profile_key=C.PROF_KEY_NONE):
701 def initialised(result): 719 def initialised(result):
702 profile, client = result 720 profile, client = result
703 d = self.host.plugins["XEP-0060"].subscribe(client.item_access_pubsub, self.getNodeName(jid.JID(pub_jid)), 721 d = self.host.plugins["XEP-0060"].subscribe(client.item_access_pubsub, self.getNodeName(jid.JID(pub_jid)),
719 """Subscribe microblogs for a list of groups or jids 737 """Subscribe microblogs for a list of groups or jids
720 @param publishers_type: type of the list of publishers (one of "GROUP" or "JID" or "ALL") 738 @param publishers_type: type of the list of publishers (one of "GROUP" or "JID" or "ALL")
721 @param publishers: list of publishers, according to "publishers_type" (list of groups or list of jids) 739 @param publishers: list of publishers, according to "publishers_type" (list of groups or list of jids)
722 @param profile_key: profile key 740 @param profile_key: profile key
723 """ 741 """
724 #TODO: custom exception
725 if publishers_type not in ["GROUP", "JID", "ALL"]:
726 raise Exception("Bad call, unknown publishers_type")
727 if publishers_type == "ALL" and publishers:
728 raise Exception("Publishers list must be empty when getting microblogs for all contacts")
729 profile, client = yield self._initialise(profile_key) 742 profile, client = yield self._initialise(profile_key)
730 #TODO: we need to use the server corresponding the the host of the jid 743 #TODO: we need to use the server corresponding the the host of the jid
731 744
732 if publishers_type == "ALL": 745 jids = self._getPublishersJIDs(publishers_type, publishers, client)
733 contacts = client.roster.getItems()
734 jids = [contact.jid.userhostJID() for contact in contacts]
735 elif publishers_type == "GROUP":
736 jids = []
737 for _group in publishers:
738 jids.extend(client.roster.getJidsFromGroup(_group))
739 elif publishers_type == 'JID':
740 jids = publishers
741 else:
742 raise UnknownType
743
744 node_ids = [self.getNodeName(publisher) for publisher in jids] 746 node_ids = [self.getNodeName(publisher) for publisher in jids]
745 d_list = yield self.host.plugins["XEP-0060"].subscribeToMany(client.item_access_pubsub, node_ids, profile_key=profile_key) 747 d_list = yield self.host.plugins["XEP-0060"].subscribeToMany(client.item_access_pubsub, node_ids, profile_key=profile_key)
746 result = yield defer.DeferredList(d_list, consumeErrors=False) 748 result = yield defer.DeferredList(d_list, consumeErrors=False)
747 defer.returnValue(result) 749 defer.returnValue(None)
748 750
749 def deleteAllGroupBlogsAndComments(self, profile_key=C.PROF_KEY_NONE): 751 def deleteAllGroupBlogsAndComments(self, profile_key=C.PROF_KEY_NONE):
750 """Delete absolutely all the microblog data that the user has posted""" 752 """Delete absolutely all the microblog data that the user has posted"""
751 calls = [self.deleteAllGroupBlogs(profile_key), self.deleteAllGroupBlogsComments(profile_key)] 753 calls = [self.deleteAllGroupBlogs(profile_key), self.deleteAllGroupBlogsComments(profile_key)]
752 return defer.DeferredList(calls) 754 return defer.DeferredList(calls)
753 755
754 def deleteAllGroupBlogs(self, profile_key=C.PROF_KEY_NONE): 756 def deleteAllGroupBlogs(self, profile_key=C.PROF_KEY_NONE):
755 """Delete all the main items and their comments that the user has posted 757 """Delete all the main items that the user has posted and their comments.
756 """ 758 """
757 def initialised(result): 759 def initialised(result):
758 profile, client = result 760 profile, client = result
759 service = client.item_access_pubsub 761 service = client.item_access_pubsub
760 jid_ = client.jid 762 jid_ = client.jid
761
762 main_node = self.getNodeName(jid_) 763 main_node = self.getNodeName(jid_)
763 d = self.host.plugins["XEP-0060"].deleteNode(service, main_node, profile_key=profile) 764
765 def cb(nodes):
766 d_list = []
767 for node in [node for node in nodes if node.endswith(main_node)]:
768 d = self.host.plugins["XEP-0060"].deleteNode(service, node, profile_key=profile)
769 d.addErrback(lambda failure: log.error(_("Deletion of node %(node)s failed: %(message)s") %
770 {'node': node, 'message': failure.getErrorMessage()}))
771 d_list.append(d)
772 return defer.DeferredList(d_list)
773
774 d = self.host.plugins["XEP-0060"].listNodes(service, profile=profile)
775 d.addCallback(cb)
764 d.addCallback(lambda dummy: log.info(_("All microblog's main items from %s have been deleted!") % jid_.userhost())) 776 d.addCallback(lambda dummy: log.info(_("All microblog's main items from %s have been deleted!") % jid_.userhost()))
765 d.addErrback(lambda failure: log.error(_("Deletion of node %(node)s failed: %(message)s") %
766 {'node': main_node, 'message': failure.getErrorMessage()}))
767 return d 777 return d
768 778
769 return self._initialise(profile_key).addCallback(initialised) 779 return self._initialise(profile_key).addCallback(initialised)
770 780
771 def deleteAllGroupBlogsComments(self, profile_key=C.PROF_KEY_NONE): 781 def deleteAllGroupBlogsComments(self, profile_key=C.PROF_KEY_NONE):
780 profile, client = result 790 profile, client = result
781 service = client.item_access_pubsub 791 service = client.item_access_pubsub
782 jids = [contact.jid.userhostJID() for contact in client.roster.getItems()] 792 jids = [contact.jid.userhostJID() for contact in client.roster.getItems()]
783 blogs = [] 793 blogs = []
784 for jid_ in jids: 794 for jid_ in jids:
795 if jid_ == client.jid.userhostJID():
796 continue # do not remove the comments on our own node
785 main_node = self.getNodeName(jid_) 797 main_node = self.getNodeName(jid_)
786 d = self.host.plugins["XEP-0060"].getItems(service, main_node, profile_key=profile) 798 d = self.host.plugins["XEP-0060"].getItems(service, main_node, profile_key=profile)
787 d.addCallback(getComments, client) 799 d.addCallback(lambda res: getComments(res[0], client))
788 d.addErrback(lambda failure, main_node: log.error(_("Retrieval of items for node %(node)s failed: %(message)s") % 800 d.addErrback(lambda failure, main_node: log.error(_("Retrieval of items for node %(node)s failed: %(message)s") %
789 {'node': main_node, 'message': failure.getErrorMessage()}), main_node) 801 {'node': main_node, 'message': failure.getErrorMessage()}), main_node)
790 blogs.append(d) 802 blogs.append(d)
791 803
792 return defer.DeferredList(blogs) 804 return defer.DeferredList(blogs)
805 except StopIteration: 817 except StopIteration:
806 continue 818 continue
807 href = link.getAttribute('href') 819 href = link.getAttribute('href')
808 service, node = self.host.plugins['XEP-0277'].parseCommentUrl(href) 820 service, node = self.host.plugins['XEP-0277'].parseCommentUrl(href)
809 d = self.host.plugins["XEP-0060"].getItems(service, node, profile_key=profile_key) 821 d = self.host.plugins["XEP-0060"].getItems(service, node, profile_key=profile_key)
810 d.addCallback(lambda items, service, node: (service, node, items), service, node) 822 d.addCallback(lambda items: (service, node, items[0]))
811 d.addErrback(lambda failure, node: log.error(_("Retrieval of comments for node %(node)s failed: %(message)s") % 823 d.addErrback(lambda failure, node: log.error(_("Retrieval of comments for node %(node)s failed: %(message)s") %
812 {'node': node, 'message': failure.getErrorMessage()}), node) 824 {'node': node, 'message': failure.getErrorMessage()}), node)
813 comments.append(d) 825 comments.append(d)
814 dlist = defer.DeferredList(comments) 826 dlist = defer.DeferredList(comments)
815 dlist.addCallback(deleteComments, client) 827 dlist.addCallback(deleteComments, client)
848 return defer.DeferredList(deletions) 860 return defer.DeferredList(deletions)
849 861
850 return self._initialise(profile_key).addCallback(initialised) 862 return self._initialise(profile_key).addCallback(initialised)
851 863
852 864
865 class DeferredItems():
866 """Helper class to retrieve items using XEP-0060"""
867
868 def __init__(self, parent, cb, eb=None, profile_key=C.PROF_KEY_NONE):
869 """
870 @param parent (GroupBlog): GroupBlog instance
871 @param cb (callable): callback method to be applied on items
872 @param eb (callable): errback method to be applied on items
873 @param profile_key (str): %(doc_profile_key)s
874 """
875 self.parent = parent
876 self.cb = cb
877 self.eb = (lambda dummy: []) if eb is None else eb
878 self.profile_key = profile_key
879
880 def get(self, node, item_ids=None, sub_id=None, rsm=None):
881 """
882 @param node (str): node identifier.
883 @param item_ids (list[str]): list of items identifiers.
884 @param sub_id (str): optional subscription identifier.
885 @param rsm (dict): RSM request data
886 @return: a deferred couple (list, dict) containing:
887 - list of microblog data
888 - RSM response data
889 """
890 if rsm is None:
891 rsm = {'max': (len(item_ids) if item_ids else MAX_ITEMS)}
892
893 def initialised(result):
894 profile, client = result
895 rsm_ = wokkel_rsm.RSMRequest(**rsm)
896 d = self.parent.host.plugins["XEP-0060"].getItems(client.item_access_pubsub,
897 node, rsm_.max,
898 item_ids, sub_id, rsm_,
899 profile_key=profile)
900
901 def cb(result):
902 d = defer.maybeDeferred(self.cb, result[0], client)
903 return d.addCallback(lambda items: (items, result[1]))
904
905 d.addCallbacks(cb, self.eb)
906 return d
907
908 #TODO: we need to use the server corresponding to the host of the jid
909 return self.parent._initialise(self.profile_key).addCallback(initialised)
910
911
912 class DeferredItemsFromMany():
913 def __init__(self, parent, cb, profile_key=C.PROF_KEY_NONE):
914 """
915 @param parent (GroupBlog): GroupBlog instance
916 @param cb (callable): callback method to be applied on items
917 @param profile_key (str): %(doc_profile_key)s
918 """
919 self.parent = parent
920 self.cb = cb
921 self.profile_key = profile_key
922
923 def __buildData(self, publishers_type, publishers, client):
924 jids = self.parent._getPublishersJIDs(publishers_type, publishers, client)
925 return {publisher: self.parent.getNodeName(publisher) for publisher in jids}
926
927 def get(self, publishers_type, publishers, sub_id=None, rsm=None):
928 """
929 @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL")
930 @param publishers (list): list of publishers, according to publishers_type (list of groups or list of jids)
931 @param sub_id (str): optional subscription identifier.
932 @param rsm (dict): RSM request data
933 @return: a deferred dict with:
934 - key: publisher (unicode)
935 - value: couple (list[dict], dict) with:
936 - the microblogs data
937 - RSM response data
938 """
939 if rsm is None:
940 rsm = {'max': MAX_ITEMS}
941
942 def initialised(result):
943 profile, client = result
944
945 data = self.__buildData(publishers_type, publishers, client)
946 rsm_ = wokkel_rsm.RSMRequest(**rsm)
947 d = self.parent.host.plugins["XEP-0060"].getItemsFromMany(client.item_access_pubsub,
948 data, rsm_.max, sub_id,
949 rsm_, profile_key=profile)
950
951 def cb(publisher):
952 def callback(result):
953 d = defer.maybeDeferred(self.cb, result[0], publisher, client)
954 d.addCallback(lambda items: (publisher.full(), (items, result[1])))
955 return d
956 return callback
957
958 def cb_list(result):
959 return {value[0]: value[1] for success, value in result if success}
960
961 def main_cb(result):
962 d_list = []
963 for publisher, d_items in result.items():
964 # XXX: trick needed as publisher is a loop variable
965 d_list.append(d_items.addCallback(cb(publisher)))
966 return defer.DeferredList(d_list, consumeErrors=False).addCallback(cb_list)
967
968 d.addCallback(main_cb)
969 return d
970
971 #TODO: we need to use the server corresponding to the host of the jid
972 return self.parent._initialise(self.profile_key).addCallback(initialised)
973
974
853 class GroupBlog_handler(XMPPHandler): 975 class GroupBlog_handler(XMPPHandler):
854 implements(iwokkel.IDisco) 976 implements(iwokkel.IDisco)
855 977
856 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): 978 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
857 return [disco.DiscoFeature(NS_GROUPBLOG)] 979 return [disco.DiscoFeature(NS_GROUPBLOG)]