Warning message and debug in case where SleekXmpp is not installed
[nepi.git] / src / nepi / resources / omf / omf_client.py
1 #
2 #    NEPI, a framework to manage network experiments
3 #    Copyright (C) 2013 INRIA
4 #
5 #    This program is free software: you can redistribute it and/or modify
6 #    it under the terms of the GNU General Public License as published by
7 #    the Free Software Foundation, either version 3 of the License, or
8 #    (at your option) any later version.
9 #
10 #    This program is distributed in the hope that it will be useful,
11 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #    GNU General Public License for more details.
14 #
15 #    You should have received a copy of the GNU General Public License
16 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
19 #         Julien Tribino <julien.tribino@inria.fr>
20
21 from nepi.util.logger import Logger
22
23
24 try:
25     import sleekxmpp
26     from sleekxmpp.exceptions import IqError, IqTimeout
27     class BaseOMFClient(sleekxmpp.ClientXMPP):
28         pass
29 except ImportError:
30     print "SleekXMPP is not installed. Without this library, \n" + \
31           " You will be not able to use OMF Resources \n"+ \
32           " If you want to install SleekXmpp : \n"+ \
33           " git clone -b develop git://github.com/fritzy/SleekXMPP.git \n"+ \
34           " cd SleekXMPP \n"+ \
35            "sudo python setup.py install\n"
36     class BaseOMFClient(object):
37         pass
38
39 import traceback
40 import xml.etree.ElementTree as ET
41
42 # inherit from BaseXmpp and XMLstream classes
43 class OMFClient(sleekxmpp.ClientXMPP, Logger): 
44     """
45     .. class:: Class Args :
46       
47         :param jid: Jabber Id (= Xmpp Slice + Date)
48         :type jid: str
49         :param password: Jabber Password (= Xmpp Password)
50         :type password: str
51
52     .. note::
53
54        This class is an XMPP Client with customized method
55
56     """
57
58     def __init__(self, jid, password):
59         """
60
61         :param jid: Jabber Id (= Xmpp Slice + Date)
62         :type jid: str
63         :param password: Jabber Password (= Xmpp Password)
64         :type password: str
65
66
67         """
68         Logger.__init__(self, "OMFClient")
69
70         sleekxmpp.ClientXMPP.__init__(self, jid, password)
71         self._ready = False
72         self._registered = False
73         self._server = None
74
75         self.register_plugin('xep_0077') # In-band registration
76         self.register_plugin('xep_0030')
77         self.register_plugin('xep_0059')
78         self.register_plugin('xep_0060') # PubSub 
79
80         self.add_event_handler("session_start", self.start)
81         self.add_event_handler("register", self.register)
82         self.add_event_handler("pubsub_publish", self.handle_omf_message)
83         
84     @property
85     def ready(self):
86         """ Check if the client is ready
87
88         """
89         return self._ready
90
91     def start(self, event):
92         """ Send presence to the Xmppp Server. This function is called directly by the sleekXmpp library
93
94         """
95         self.send_presence()
96         self._ready = True
97         self._server = "pubsub.%s" % self.boundjid.domain
98
99     def register(self, iq):
100         """  Register to the Xmppp Server. This function is called directly by the sleekXmpp library
101
102         """
103         if self._registered:
104             msg = " %s already registered!" % self.boundjid
105             self.info(msg)
106             return 
107
108         resp = self.Iq()
109         resp['type'] = 'set'
110         resp['register']['username'] = self.boundjid.user
111         resp['register']['password'] = self.password
112
113         try:
114             resp.send(now=True)
115             msg = " Account created for %s!" % self.boundjid
116             self.info(msg)
117             self._registered = True
118         except IqError as e:
119             msg = " Could not register account: %s" % e.iq['error']['text']
120             self.error(msg)
121         except IqTimeout:
122             msg = " No response from server."
123             self.error(msg)
124
125     def unregister(self):
126         """  Unregister from the Xmppp Server.
127
128         """
129         try:
130             self.plugin['xep_0077'].cancel_registration(
131                 ifrom=self.boundjid.full)
132             msg = " Account unregistered for %s!" % self.boundjid
133             self.info(msg)
134         except IqError as e:
135             msg = " Could not unregister account: %s" % e.iq['error']['text']
136             self.error(msg)
137         except IqTimeout:
138             msg = " No response from server."
139             self.error(msg)
140
141     def nodes(self):
142         """  Get all the nodes of the Xmppp Server.
143
144         """
145         try:
146             result = self['xep_0060'].get_nodes(self._server)
147             for item in result['disco_items']['items']:
148                 msg = ' - %s' % str(item)
149                 self.debug(msg)
150             return result
151         except:
152             error = traceback.format_exc()
153             msg = 'Could not retrieve node list.\ntraceback:\n%s' % error
154             self.error(msg)
155
156     def subscriptions(self):
157         """  Get all the subscriptions of the Xmppp Server.
158
159         """
160         try:
161             result = self['xep_0060'].get_subscriptions(self._server)
162                 #self.boundjid.full)
163             for node in result['node']:
164                 msg = ' - %s' % str(node)
165                 self.debug(msg)
166             return result
167         except:
168             error = traceback.format_exc()
169             msg = ' Could not retrieve subscriptions.\ntraceback:\n%s' % error
170             self.error(msg)
171
172     def create(self, node):
173         """  Create the topic corresponding to the node
174
175         :param node: Name of the topic, corresponding to the node (ex : omf.plexus.wlab17)
176         :type node: str
177
178         """
179         msg = " Create Topic : " + node
180         self.info(msg)
181    
182         config = self['xep_0004'].makeForm('submit')
183         config.add_field(var='pubsub#node_type', value='leaf')
184         config.add_field(var='pubsub#notify_retract', value='0')
185         config.add_field(var='pubsub#publish_model', value='open')
186         config.add_field(var='pubsub#persist_items', value='1')
187         config.add_field(var='pubsub#max_items', value='1')
188         config.add_field(var='pubsub#title', value=node)
189
190         try:
191             self['xep_0060'].create_node(self._server, node, config = config)
192         except:
193             error = traceback.format_exc()
194             msg = ' Could not create topic: %s\ntraceback:\n%s' % (node, error)
195             self.error(msg)
196
197     def delete(self, node):
198         """  Delete the topic corresponding to the node
199
200         :param node: Name of the topic, corresponding to the node (ex : omf.plexus.wlab17)
201         :type node: str
202
203         """
204         # To check if the queue are well empty at the end
205         #print " length of the queue : " + str(self.send_queue.qsize())
206         #print " length of the queue : " + str(self.event_queue.qsize())
207         try:
208             self['xep_0060'].delete_node(self._server, node)
209             msg = ' Deleted node: %s' % node
210             self.info(msg)
211         except:
212             error = traceback.format_exc()
213             msg = ' Could not delete topic: %s\ntraceback:\n%s' % (node, error)
214             self.error(msg)
215     
216     def publish(self, data, node):
217         """  Publish the data to the corresponding topic
218
219         :param data: Data that will be published
220         :type data: str
221         :param node: Name of the topic
222         :type node: str
223
224         """ 
225
226         msg = " Publish to Topic : " + node
227         self.info(msg)
228         try:
229             result = self['xep_0060'].publish(self._server,node,payload=data)
230             # id = result['pubsub']['publish']['item']['id']
231             # print('Published at item id: %s' % id)
232         except:
233             error = traceback.format_exc()
234             msg = ' Could not publish to: %s\ntraceback:\n%s' % (node, error)
235             self.error(msg)
236
237     def get(self, data):
238         """  Get the item
239
240         :param data: data from which the items will be get back
241         :type data: str
242
243
244         """
245         try:
246             result = self['xep_0060'].get_item(self._server, self.boundjid,
247                 data)
248             for item in result['pubsub']['items']['substanzas']:
249                 msg = 'Retrieved item %s: %s' % (item['id'], tostring(item['payload']))
250                 self.debug(msg)
251         except:
252             error = traceback.format_exc()
253             msg = ' Could not retrieve item %s from topic %s\ntraceback:\n%s' \
254                     % (data, self.boundjid, error)
255             self.error(msg)
256
257     def retract(self, data):
258         """  Retract the item
259
260         :param data: data from which the item will be retracted
261         :type data: str
262
263         """
264         try:
265             result = self['xep_0060'].retract(self._server, self.boundjid, data)
266             msg = ' Retracted item %s from topic %s' % (data, self.boundjid)
267             self.debug(msg)
268         except:
269             error = traceback.format_exc()
270             msg = 'Could not retract item %s from topic %s\ntraceback:\n%s' \
271                     % (data, self.boundjid, error)
272             self.error(msg)
273
274     def purge(self):
275         """  Purge the information in the server
276
277         """
278         try:
279             result = self['xep_0060'].purge(self._server, self.boundjid)
280             msg = ' Purged all items from topic %s' % self.boundjid
281             self.debug(msg)
282         except:
283             error = traceback.format_exc()
284             msg = ' Could not purge items from topic %s\ntraceback:\n%s' \
285                     % (self.boundjid, error)
286             self.error(msg)
287
288     def subscribe(self, node):
289         """ Subscribe to a topic
290
291         :param node: Name of the topic
292         :type node: str
293
294         """
295         try:
296             result = self['xep_0060'].subscribe(self._server, node)
297             msg = ' Subscribed %s to topic %s' \
298                     % (self.boundjid.user, node)
299             #self.info(msg)
300             self.debug(msg)
301         except:
302             error = traceback.format_exc()
303             msg = ' Could not subscribe %s to topic %s\ntraceback:\n%s' \
304                     % (self.boundjid.bare, node, error)
305             self.error(msg)
306
307     def unsubscribe(self, node):
308         """ Unsubscribe to a topic
309
310         :param node: Name of the topic
311         :type node: str
312
313         """
314         try:
315             result = self['xep_0060'].unsubscribe(self._server, node)
316             msg = ' Unsubscribed %s from topic %s' % (self.boundjid.bare, node)
317             self.debug(msg)
318         except:
319             error = traceback.format_exc()
320             msg = ' Could not unsubscribe %s from topic %s\ntraceback:\n%s' \
321                     % (self.boundjid.bare, node, error)
322             self.error(msg)
323
324     def _check_for_tag(self, root, namespaces, tag):
325         """  Check if an element markup is in the ElementTree
326
327         :param root: Root of the tree
328         :type root: ElementTree Element
329         :param namespaces: Namespaces of the element
330         :type namespaces: str
331         :param tag: Tag that will search in the tree
332         :type tag: str
333
334         """
335         for element in root.iter(namespaces+tag):
336             if element.text:
337                 return element
338             else : 
339                 return None    
340
341     def _check_output(self, root, namespaces):
342         """ Check the significative element in the answer and display it
343
344         :param root: Root of the tree
345         :type root: ElementTree Element
346         :param namespaces: Namespaces of the tree
347         :type namespaces: str
348
349         """
350         fields = ["TARGET", "REASON", "PATH", "APPID", "VALUE"]
351         response = ""
352         for elt in fields:
353             msg = self._check_for_tag(root, namespaces, elt)
354             if msg is not None:
355                 response = response + " " + msg.text + " :"
356         deb = self._check_for_tag(root, namespaces, "MESSAGE")
357         if deb is not None:
358             msg = response + " " + deb.text
359             self.debug(msg)
360         else :
361             self.info(response)
362
363     def handle_omf_message(self, iq):
364         """ Handle published/received message 
365
366         :param iq: Stanzas that is currently published/received
367         :type iq: Iq Stanza
368
369         """
370         namespaces = "{http://jabber.org/protocol/pubsub}"
371         for i in iq['pubsub_event']['items']:
372             root = ET.fromstring(str(i))
373             self._check_output(root, namespaces)
374
375