PubSub support: browse, subscribe and unsubscribe; posting atom entries to groups.
This commit is contained in:
		
							parent
							
								
									4682b5bed7
								
							
						
					
					
						commit
						8f4b972a62
					
				
					 3 changed files with 214 additions and 8 deletions
				
			
		|  | @ -35,6 +35,7 @@ from common import gajim | ||||||
| from common import dataforms | from common import dataforms | ||||||
| from common import atom | from common import atom | ||||||
| from common.commands import ConnectionCommands | from common.commands import ConnectionCommands | ||||||
|  | from common.pubsub import ConnectionPubSub | ||||||
| 
 | 
 | ||||||
| STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', | STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', | ||||||
| 	'invisible'] | 	'invisible'] | ||||||
|  | @ -1057,11 +1058,12 @@ class ConnectionVcard: | ||||||
| 			else: | 			else: | ||||||
| 				self.dispatch('VCARD', vcard) | 				self.dispatch('VCARD', vcard) | ||||||
| 
 | 
 | ||||||
| class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands): | class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub): | ||||||
| 	def __init__(self): | 	def __init__(self): | ||||||
| 		ConnectionVcard.__init__(self) | 		ConnectionVcard.__init__(self) | ||||||
| 		ConnectionBytestream.__init__(self) | 		ConnectionBytestream.__init__(self) | ||||||
| 		ConnectionCommands.__init__(self) | 		ConnectionCommands.__init__(self) | ||||||
|  | 		ConnectionPubSub.__init__(self) | ||||||
| 		# List of IDs we are waiting answers for {id: (type_of_request, data), } | 		# List of IDs we are waiting answers for {id: (type_of_request, data), } | ||||||
| 		self.awaiting_answers = {} | 		self.awaiting_answers = {} | ||||||
| 		# List of IDs that will produce a timeout is answer doesn't arrive | 		# List of IDs that will produce a timeout is answer doesn't arrive | ||||||
|  | @ -1857,6 +1859,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, | ||||||
| 			common.xmpp.NS_DISCO_INFO) | 			common.xmpp.NS_DISCO_INFO) | ||||||
| 		con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get', | 		con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get', | ||||||
| 			common.xmpp.NS_DISCO_ITEMS) | 			common.xmpp.NS_DISCO_ITEMS) | ||||||
|  | 		con.RegisterHandler('iq', self._PubSubCB, 'result') | ||||||
| 		con.RegisterHandler('iq', self._ErrorCB, 'error') | 		con.RegisterHandler('iq', self._ErrorCB, 'error') | ||||||
| 		con.RegisterHandler('iq', self._IqCB) | 		con.RegisterHandler('iq', self._IqCB) | ||||||
| 		con.RegisterHandler('iq', self._StanzaArrivedCB) | 		con.RegisterHandler('iq', self._StanzaArrivedCB) | ||||||
|  |  | ||||||
							
								
								
									
										51
									
								
								src/common/pubsub.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/common/pubsub.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | ||||||
|  | import xmpp | ||||||
|  | import gajim | ||||||
|  | 
 | ||||||
|  | class ConnectionPubSub: | ||||||
|  | 	def __init__(self): | ||||||
|  | 		self.__callbacks={} | ||||||
|  | 
 | ||||||
|  | 	def send_pb_subscription_query(self, jid, cb, *args, **kwargs): | ||||||
|  | 		query = xmpp.Iq('get', to=jid) | ||||||
|  | 		pb = query.addChild('pubsub', {'xmlns': xmpp.NS_PUBSUB}) | ||||||
|  | 		pb.addChild('subscriptions') | ||||||
|  | 
 | ||||||
|  | 		id = self.connection.send(query) | ||||||
|  | 
 | ||||||
|  | 		self.__callbacks[id]=(cb, args, kwargs) | ||||||
|  | 
 | ||||||
|  | 	def send_pb_subscribe(self, jid, node, cb, *args, **kwargs): | ||||||
|  | 		our_jid = gajim.get_jid_from_account(self.name) | ||||||
|  | 		query = xmpp.Iq('set', to=jid) | ||||||
|  | 		pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) | ||||||
|  | 		pb.addChild('subscribe', {'node': node, 'jid': our_jid}) | ||||||
|  | 
 | ||||||
|  | 		id = self.connection.send(query) | ||||||
|  | 
 | ||||||
|  | 		self.__callbacks[id]=(cb, args, kwargs) | ||||||
|  | 
 | ||||||
|  | 	def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs): | ||||||
|  | 		our_jid = gajim.get_jid_from_account(self.name) | ||||||
|  | 		query = xmpp.Iq('set', to=jid) | ||||||
|  | 		pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) | ||||||
|  | 		pb.addChild('unsubscribe', {'node': node, 'jid': our_jid}) | ||||||
|  | 
 | ||||||
|  | 		id = self.connection.send(query) | ||||||
|  | 
 | ||||||
|  | 		self.__callbacks[id]=(cb, args, kwargs) | ||||||
|  | 
 | ||||||
|  | 	def send_pb_publish(self, jid, node, item, id): | ||||||
|  | 		'''Publish item to a node.''' | ||||||
|  | 		query = xmpp.Iq('set', to=jid) | ||||||
|  | 		e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) | ||||||
|  | 		e = e.addChild('publish', {'node': node}) | ||||||
|  | 		e = e.addChild('item', {'id': id}, [item])	# TODO: we should generate id... or we shouldn't? | ||||||
|  | 
 | ||||||
|  | 		self.connection.send(query) | ||||||
|  | 
 | ||||||
|  | 	def _PubSubCB(self, conn, stanza): | ||||||
|  | 		try: | ||||||
|  | 			cb, args, kwargs = self.__callbacks.pop(stanza.getID()) | ||||||
|  | 			cb(conn, stanza, *args, **kwargs) | ||||||
|  | 		except KeyError: | ||||||
|  | 			pass | ||||||
							
								
								
									
										166
									
								
								src/disco.py
									
										
									
									
									
								
							
							
						
						
									
										166
									
								
								src/disco.py
									
										
									
									
									
								
							|  | @ -56,6 +56,7 @@ import pango | ||||||
| import dialogs | import dialogs | ||||||
| import tooltips | import tooltips | ||||||
| import gtkgui_helpers | import gtkgui_helpers | ||||||
|  | import groups | ||||||
| 
 | 
 | ||||||
| from common import gajim | from common import gajim | ||||||
| from common import xmpp | from common import xmpp | ||||||
|  | @ -217,6 +218,7 @@ class ServicesCache: | ||||||
| 		self.account = account | 		self.account = account | ||||||
| 		self._items = CacheDictionary(15, getrefresh = False) | 		self._items = CacheDictionary(15, getrefresh = False) | ||||||
| 		self._info = CacheDictionary(15, getrefresh = False) | 		self._info = CacheDictionary(15, getrefresh = False) | ||||||
|  | 		self._subscriptions = CacheDictionary(5, getrefresh=False) | ||||||
| 		self._cbs = {} | 		self._cbs = {} | ||||||
| 
 | 
 | ||||||
| 	def _clean_closure(self, cb, type, addr): | 	def _clean_closure(self, cb, type, addr): | ||||||
|  | @ -573,7 +575,7 @@ _('Without a connection, you can not browse available services')) | ||||||
| 		self.connect_style_event(opts[0], opts[1]) | 		self.connect_style_event(opts[0], opts[1]) | ||||||
| 	 | 	 | ||||||
| 	def destroy(self, chain = False): | 	def destroy(self, chain = False): | ||||||
| 		'''Close the browser. This can optionally close it's children and | 		'''Close the browser. This can optionally close its children and | ||||||
| 		propagate to the parent. This should happen on actions like register, | 		propagate to the parent. This should happen on actions like register, | ||||||
| 		or join to kill off the entire browser chain.''' | 		or join to kill off the entire browser chain.''' | ||||||
| 		if self.dying: | 		if self.dying: | ||||||
|  | @ -596,7 +598,8 @@ _('Without a connection, you can not browse available services')) | ||||||
| 				child.destroy(chain = chain) | 				child.destroy(chain = chain) | ||||||
| 				self.children.remove(child) | 				self.children.remove(child) | ||||||
| 		if self.parent: | 		if self.parent: | ||||||
| 			self.parent.children.remove(self) | 			if self in self.parent.children: | ||||||
|  | 				self.parent.children.remove(self) | ||||||
| 			if chain and not self.parent.children: | 			if chain and not self.parent.children: | ||||||
| 				self.parent.destroy(chain = chain) | 				self.parent.destroy(chain = chain) | ||||||
| 				self.parent = None | 				self.parent = None | ||||||
|  | @ -1670,10 +1673,24 @@ def PubSubBrowser(account, jid, node): | ||||||
| 
 | 
 | ||||||
| class DiscussionGroupsBrowser(AgentBrowser): | class DiscussionGroupsBrowser(AgentBrowser): | ||||||
| 	''' For browsing pubsub-based discussion groups service. ''' | 	''' For browsing pubsub-based discussion groups service. ''' | ||||||
|  | 	def __init__(self, account, jid, node): | ||||||
|  | 		AgentBrowser.__init__(self, account, jid, node) | ||||||
|  | 
 | ||||||
|  | 		# this will become set object when we get subscriptions; None means | ||||||
|  | 		# we don't know yet which groups are subscribed | ||||||
|  | 		self.subscriptions = None | ||||||
|  | 
 | ||||||
|  | 		# this will become our action widgets when we create them; None means | ||||||
|  | 		# we don't have them yet (needed for check in callback) | ||||||
|  | 		self.subscribe_button = None | ||||||
|  | 		self.unsubscribe_button = None | ||||||
|  | 
 | ||||||
|  | 		gajim.connections[account].send_pb_subscription_query(jid, self._subscriptionsCB) | ||||||
|  | 
 | ||||||
| 	def _create_treemodel(self): | 	def _create_treemodel(self): | ||||||
| 		''' Create treemodel for the window. ''' | 		''' Create treemodel for the window. ''' | ||||||
| 		# JID, node, name (with description) - pango markup, subscribed? | 		# JID, node, name (with description) - pango markup, dont have info?, subscribed? | ||||||
| 		model = gtk.ListStore(str, str, str, bool) | 		model = gtk.ListStore(str, str, str, bool, bool) | ||||||
| 		model.set_sort_column_id(3, gtk.SORT_ASCENDING) | 		model.set_sort_column_id(3, gtk.SORT_ASCENDING) | ||||||
| 		self.window.services_treeview.set_model(model) | 		self.window.services_treeview.set_model(model) | ||||||
| 
 | 
 | ||||||
|  | @ -1691,24 +1708,53 @@ class DiscussionGroupsBrowser(AgentBrowser): | ||||||
| 		renderer = gtk.CellRendererToggle() | 		renderer = gtk.CellRendererToggle() | ||||||
| 		col = gtk.TreeViewColumn(_('Subscribed')) | 		col = gtk.TreeViewColumn(_('Subscribed')) | ||||||
| 		col.pack_start(renderer) | 		col.pack_start(renderer) | ||||||
| 		col.set_attributes(renderer, active=3) | 		col.set_attributes(renderer, inconsistent=3, active=4) | ||||||
| 		col.set_resizable(False) | 		col.set_resizable(False) | ||||||
| 		self.window.services_treeview.insert_column(col, -1) | 		self.window.services_treeview.insert_column(col, -1) | ||||||
| 
 | 
 | ||||||
| 		self.window.services_treeview.set_headers_visible(True) | 		self.window.services_treeview.set_headers_visible(True) | ||||||
| 
 | 
 | ||||||
|  | 	def _add_item(self, model, jid, node, item, force): | ||||||
|  | 		''' Called when we got basic information about new node from query. | ||||||
|  | 		Show the item. ''' | ||||||
|  | 		name = item.get('name', '') | ||||||
|  | 
 | ||||||
|  | 		if self.subscriptions is not None: | ||||||
|  | 			dunno = False | ||||||
|  | 			subscribed = name in self.subscriptions | ||||||
|  | 		else: | ||||||
|  | 			dunno = True | ||||||
|  | 			subscribed = False | ||||||
|  | 
 | ||||||
|  | 		name = gtkgui_helpers.escape_for_pango_markup(name) | ||||||
|  | 		name = '<b>%s</b>' % name | ||||||
|  | 
 | ||||||
|  | 		model.append((jid, node, name, dunno, subscribed)) | ||||||
|  | 
 | ||||||
| 	def _add_actions(self): | 	def _add_actions(self): | ||||||
|  | 		self.post_button = gtk.Button(label=_('New post'), use_underline=True) | ||||||
|  | 		self.post_button.set_sensitive(False) | ||||||
|  | 		self.post_button.connect('clicked', self.on_post_button_clicked) | ||||||
|  | 		self.window.action_buttonbox.add(self.post_button) | ||||||
|  | 		self.post_button.show_all() | ||||||
|  | 
 | ||||||
| 		self.subscribe_button = gtk.Button(label=_('_Subscribe'), use_underline=True) | 		self.subscribe_button = gtk.Button(label=_('_Subscribe'), use_underline=True) | ||||||
|  | 		self.subscribe_button.set_sensitive(False) | ||||||
| 		self.subscribe_button.connect('clicked', self.on_subscribe_button_clicked) | 		self.subscribe_button.connect('clicked', self.on_subscribe_button_clicked) | ||||||
| 		self.window.action_buttonbox.add(self.subscribe_button) | 		self.window.action_buttonbox.add(self.subscribe_button) | ||||||
| 		self.subscribe_button.show_all() | 		self.subscribe_button.show_all() | ||||||
| 
 | 
 | ||||||
| 		self.unsubscribe_button = gtk.Button(label=_('_Unsubscribe'), use_underline=True) | 		self.unsubscribe_button = gtk.Button(label=_('_Unsubscribe'), use_underline=True) | ||||||
|  | 		self.unsubscribe_button.set_sensitive(False) | ||||||
| 		self.unsubscribe_button.connect('clicked', self.on_unsubscribe_button_clicked) | 		self.unsubscribe_button.connect('clicked', self.on_unsubscribe_button_clicked) | ||||||
| 		self.window.action_buttonbox.add(self.unsubscribe_button) | 		self.window.action_buttonbox.add(self.unsubscribe_button) | ||||||
| 		self.unsubscribe_button.show_all() | 		self.unsubscribe_button.show_all() | ||||||
| 
 | 
 | ||||||
| 	def _clean_actions(self): | 	def _clean_actions(self): | ||||||
|  | 		if self.post_button is not None: | ||||||
|  | 			self.post_button.destroy() | ||||||
|  | 			self.post_button = None | ||||||
|  | 
 | ||||||
| 		if self.subscribe_button is not None: | 		if self.subscribe_button is not None: | ||||||
| 			self.subscribe_button.destroy() | 			self.subscribe_button.destroy() | ||||||
| 			self.subscribe_button = None | 			self.subscribe_button = None | ||||||
|  | @ -1717,8 +1763,114 @@ class DiscussionGroupsBrowser(AgentBrowser): | ||||||
| 			self.unsubscribe_button.destroy() | 			self.unsubscribe_button.destroy() | ||||||
| 			self.unsubscribe_button = None | 			self.unsubscribe_button = None | ||||||
| 
 | 
 | ||||||
| 	def on_subscribe_button_clicked(*x): pass | 	def update_actions(self): | ||||||
| 	def on_unsubscribe_button_clicked(*x): pass | 		'''Called when user selected a row. Make subscribe/unsubscribe buttons | ||||||
|  | 		sensitive appropriatelly.''' | ||||||
|  | 		# we have nothing to do if we don't have buttons... | ||||||
|  | 		if self.subscribe_button is None: return | ||||||
|  | 
 | ||||||
|  | 		model, iter = self.window.services_treeview.get_selection().get_selected() | ||||||
|  | 		if not iter or self.subscriptions is None: | ||||||
|  | 			# no item selected or no subscriptions info, all buttons are insensitive | ||||||
|  | 			self.post_button.set_sensitive(False) | ||||||
|  | 			self.subscribe_button.set_sensitive(False) | ||||||
|  | 			self.unsubscribe_button.set_sensitive(False) | ||||||
|  | 		else: | ||||||
|  | 			subscribed = model.get_value(iter, 4) # 4 = subscribed? | ||||||
|  | 			self.post_button.set_sensitive(subscribed) | ||||||
|  | 			self.subscribe_button.set_sensitive(not subscribed) | ||||||
|  | 			self.unsubscribe_button.set_sensitive(subscribed) | ||||||
|  | 
 | ||||||
|  | 	def on_post_button_clicked(self, widget): | ||||||
|  | 		'''Called when 'post' button is pressed. Open window to create post''' | ||||||
|  | 		model, iter = self.window.services_treeview.get_selection().get_selected() | ||||||
|  | 		if iter is None: return | ||||||
|  | 
 | ||||||
|  | 		groupnode = model.get_value(iter, 1)	# 1 = groupnode | ||||||
|  | 
 | ||||||
|  | 		groups.GroupsPostWindow(self.account, self.jid, groupnode) | ||||||
|  | 
 | ||||||
|  | 	def on_subscribe_button_clicked(self, widget): | ||||||
|  | 		'''Called when 'subscribe' button is pressed. Send subscribtion request.''' | ||||||
|  | 		model, iter = self.window.services_treeview.get_selection().get_selected() | ||||||
|  | 		if iter is None: return | ||||||
|  | 
 | ||||||
|  | 		groupnode = model.get_value(iter, 1)	# 1 = groupnode | ||||||
|  | 
 | ||||||
|  | 		gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode, self._subscribeCB, groupnode) | ||||||
|  | 		 | ||||||
|  | 	def on_unsubscribe_button_clicked(self, widget): | ||||||
|  | 		'''Called when 'unsubscribe' button is pressed. Send unsubscription request.''' | ||||||
|  | 		model, iter = self.window.services_treeview.get_selection().get_selected() | ||||||
|  | 		if iter is None: return | ||||||
|  | 
 | ||||||
|  | 		groupnode = model.get_value(iter, 1)    # 1 = groupnode | ||||||
|  | 		 | ||||||
|  | 		gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._unsubscribeCB, groupnode) | ||||||
|  | 
 | ||||||
|  | 	def _subscriptionsCB(self, conn, request): | ||||||
|  | 		''' We got the subscribed groups list stanza. Now, if we already | ||||||
|  | 		have items on the list, we should actualize them. ''' | ||||||
|  | 		print 0 | ||||||
|  | 		try: | ||||||
|  | 			subscriptions = request.getTag('pubsub').getTag('subscriptions') | ||||||
|  | 		except: | ||||||
|  | 			return  | ||||||
|  | 
 | ||||||
|  | 		print 1 | ||||||
|  | 		groups = set() | ||||||
|  | 		for child in subscriptions.getTags('subscription'): | ||||||
|  | 			print 2, repr(child), str(child) | ||||||
|  | 			groups.add(child['node']) | ||||||
|  | 		print 3, groups | ||||||
|  | 
 | ||||||
|  | 		self.subscriptions = groups | ||||||
|  | 
 | ||||||
|  | 		# try to setup existing items in model | ||||||
|  | 		model = self.window.services_treeview.get_model() | ||||||
|  | 		print 4 | ||||||
|  | 		for row in model: | ||||||
|  | 			print 5 | ||||||
|  | 			# 1 = group node | ||||||
|  | 			# 3 = insensitive checkbox for subscribed | ||||||
|  | 			# 4 = subscribed? | ||||||
|  | 			groupnode = row[1] | ||||||
|  | 			row[3]=False | ||||||
|  | 			row[4]=groupnode in groups | ||||||
|  | 		print 6 | ||||||
|  | 
 | ||||||
|  | 		# we now know subscriptions, update button states | ||||||
|  | 		self.update_actions() | ||||||
|  | 
 | ||||||
|  | 		raise xmpp.NodeProcessed | ||||||
|  | 
 | ||||||
|  | 	def _subscribeCB(self, conn, request, groupnode): | ||||||
|  | 		'''We have just subscribed to a node. Update UI''' | ||||||
|  | 		self.subscriptions.add(groupnode) | ||||||
|  | 
 | ||||||
|  | 		model = self.window.services_treeview.get_model() | ||||||
|  | 		for row in model: | ||||||
|  | 			if row[1] == groupnode: # 1 = groupnode | ||||||
|  | 				row[4]=True | ||||||
|  | 				break | ||||||
|  | 
 | ||||||
|  | 		self.update_actions() | ||||||
|  | 
 | ||||||
|  | 		raise xmpp.NodeProcessed | ||||||
|  | 
 | ||||||
|  | 	def _unsubscribeCB(self, conn, request, groupnode): | ||||||
|  | 		'''We have just unsubscribed from a node. Update UI''' | ||||||
|  | 		self.subscriptions.remove(groupnode) | ||||||
|  | 
 | ||||||
|  | 		model = self.window.services_treeview.get_model() | ||||||
|  | 		for row in model: | ||||||
|  | 			if row[1] == groupnode: # 1 = groupnode | ||||||
|  | 				row[4]=False | ||||||
|  | 				break | ||||||
|  | 
 | ||||||
|  | 		self.update_actions() | ||||||
|  | 
 | ||||||
|  | 		raise xmpp.NodeProcessed | ||||||
| 
 | 
 | ||||||
| # Fill the global agent type info dictionary | # Fill the global agent type info dictionary | ||||||
| _agent_type_info = _gen_agent_type_info() | _agent_type_info = _gen_agent_type_info() | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue