42f1d516d59f3780e0a21809dbef3a96b19b9a73
[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 from nepi.resources.omf.omf6_parser import OMF6Parser
23 try:
24     import sleekxmpp
25     from sleekxmpp.exceptions import IqError, IqTimeout
26     class BaseOMFClient(sleekxmpp.ClientXMPP):
27         pass
28 except ImportError:
29     msg = ("SleekXMPP is not installed. Without this library "
30           "you will be not able to use OMF Resources "
31           "if you want to install SleekXmpp: \n"
32           " git clone -b develop git://github.com/fritzy/SleekXMPP.git \n"
33           " cd SleekXMPP \n"
34           " sudo python setup.py install\n")
35
36     logger = Logger("BaseOMFClient")
37     logger.debug(msg)
38
39     class BaseOMFClient(object):
40         pass
41
42 import traceback
43 import xml.etree.ElementTree as ET
44
45 # inherit from BaseXmpp and XMLstream classes
46 class OMFClient(BaseOMFClient, Logger): 
47     """
48     .. class:: Class Args :
49       
50         :param jid: Jabber Id (= Xmpp Slice + Date)
51         :type jid: str
52         :param password: Jabber Password (= Xmpp Password)
53         :type password: str
54
55     .. note::
56
57        This class is an XMPP Client with customized method
58
59     """
60
61     def __init__(self, jid, password):
62         """
63
64         :param jid: Jabber Id (= Xmpp Slice + Date)
65         :type jid: str
66         :param password: Jabber Password (= Xmpp Password)
67         :type password: str
68
69
70         """
71         Logger.__init__(self, "OMFClient")
72
73         sleekxmpp.ClientXMPP.__init__(self, jid, password)
74         self._ready = False
75         self._registered = False
76         self._server = None
77         self._parser = None
78
79         self.register_plugin('xep_0077') # In-band registration
80         self.register_plugin('xep_0030')
81         self.register_plugin('xep_0059')
82         self.register_plugin('xep_0060') # PubSub 
83
84         self.add_event_handler("session_start", self.start)
85         self.add_event_handler("register", self.register)
86         self.add_event_handler("pubsub_publish", self.handle_omf_message)
87
88         #Init the parser
89         self._init_parser()
90         
91     def _init_parser(self):
92         """ Init the parser depending on the OMF Version
93
94         """
95         self._parser = OMF6Parser()
96
97     @property
98     def ready(self):
99         """ Check if the client is ready
100
101         """
102         return self._ready
103
104     def start(self, event):
105         """ Send presence to the Xmppp Server. This function is called directly by the sleekXmpp library
106
107         """
108         self.send_presence()
109         self._ready = True
110         self._server = "pubsub.%s" % self.boundjid.domain
111
112     def register(self, iq):
113         """  Register to the Xmppp Server. This function is called directly by the sleekXmpp library
114
115         """
116         if self._registered:
117             msg = " %s already registered!" % self.boundjid
118             self.info(msg)
119             return 
120
121         resp = self.Iq()
122         resp['type'] = 'set'
123         resp['register']['username'] = self.boundjid.user
124         resp['register']['password'] = self.password
125
126         try:
127             resp.send(now=True)
128             msg = " Account created for %s!" % self.boundjid
129             self.info(msg)
130             self._registered = True
131         except IqError as e:
132             msg = " Could not register account: %s" % e.iq['error']['text']
133             self.error(msg)
134         except IqTimeout:
135             msg = " No response from server."
136             self.error(msg)
137
138     def unregister(self):
139         """  Unregister from the Xmppp Server.
140
141         """
142         try:
143             self.plugin['xep_0077'].cancel_registration(
144                 ifrom=self.boundjid.full)
145             msg = " Account unregistered for %s!" % self.boundjid
146             self.info(msg)
147         except IqError as e:
148             msg = " Could not unregister account: %s" % e.iq['error']['text']
149             self.error(msg)
150         except IqTimeout:
151             msg = " No response from server."
152             self.error(msg)
153
154     def nodes(self):
155         """  Get all the nodes of the Xmppp Server.
156
157         """
158         try:
159             result = self['xep_0060'].get_nodes(self._server)
160             for item in result['disco_items']['items']:
161                 msg = ' - %s' % str(item)
162                 self.debug(msg)
163             return result
164         except:
165             error = traceback.format_exc()
166             msg = 'Could not retrieve node list.\ntraceback:\n%s' % error
167             self.error(msg)
168
169     def subscriptions(self):
170         """  Get all the subscriptions of the Xmppp Server.
171
172         """
173         try:
174             result = self['xep_0060'].get_subscriptions(self._server)
175                 #self.boundjid.full)
176             for node in result['node']:
177                 msg = ' - %s' % str(node)
178                 self.debug(msg)
179             return result
180         except:
181             error = traceback.format_exc()
182             msg = ' Could not retrieve subscriptions.\ntraceback:\n%s' % error
183             self.error(msg)
184
185     def create(self, node):
186         """  Create the topic corresponding to the node
187
188         :param node: Name of the topic, corresponding to the node (ex : omf.plexus.wlab17)
189         :type node: str
190
191         """
192         msg = " Create Topic : " + node
193         self.info(msg)
194    
195         config = self['xep_0004'].makeForm('submit')
196         config.add_field(var='pubsub#node_type', value='leaf')
197         config.add_field(var='pubsub#notify_retract', value='0')
198         config.add_field(var='pubsub#publish_model', value='open')
199         config.add_field(var='pubsub#persist_items', value='1')
200         config.add_field(var='pubsub#max_items', value='1')
201         config.add_field(var='pubsub#title', value=node)
202
203         try:
204             self['xep_0060'].create_node(self._server, node, config = config)
205         except:
206             #error = traceback.format_exc()
207             #msg = ' Could not create topic: %s\ntraceback:\n%s' % (node, error)
208             msg = 'Could not create the topic : '+node+' . Maybe the topic already exists'
209             self.error(msg)
210
211     def delete(self, node):
212         """  Delete the topic corresponding to the node
213
214         :param node: Name of the topic, corresponding to the node (ex : omf.plexus.wlab17)
215         :type node: str
216
217         """
218         # To check if the queue are well empty at the end
219         #print " length of the queue : " + str(self.send_queue.qsize())
220         #print " length of the queue : " + str(self.event_queue.qsize())
221         try:
222             self['xep_0060'].delete_node(self._server, node)
223             msg = ' Deleted node: %s' % node
224             self.info(msg)
225         except:
226             #error = traceback.format_exc()
227             #msg = ' Could not delete topic: %s\ntraceback:\n%s' % (node, error)
228             msg = 'Could not delete the topic : '+node+' . Maybe It is not the owner of the topic'
229             self.error(msg)
230     
231     def publish(self, data, node):
232         """  Publish the data to the corresponding topic
233
234         :param data: Data that will be published
235         :type data: str
236         :param node: Name of the topic
237         :type node: str
238
239         """ 
240
241         msg = " Publish to Topic : " + node
242         self.info(msg)
243         try:
244             result = self['xep_0060'].publish(self._server,node,payload=data)
245             # id = result['pubsub']['publish']['item']['id']
246             # print('Published at item id: %s' % id)
247         except:
248             error = traceback.format_exc()
249             msg = ' Could not publish to: %s\ntraceback:\n%s' % (node, error)
250             self.error(msg)
251
252     def get(self, data):
253         """  Get the item
254
255         :param data: data from which the items will be get back
256         :type data: str
257
258
259         """
260         try:
261             result = self['xep_0060'].get_item(self._server, self.boundjid,
262                 data)
263             for item in result['pubsub']['items']['substanzas']:
264                 msg = 'Retrieved item %s: %s' % (item['id'], tostring(item['payload']))
265                 self.debug(msg)
266         except:
267             error = traceback.format_exc()
268             msg = ' Could not retrieve item %s from topic %s\ntraceback:\n%s' \
269                     % (data, self.boundjid, error)
270             self.error(msg)
271
272     def retract(self, data):
273         """  Retract the item
274
275         :param data: data from which the item will be retracted
276         :type data: str
277
278         """
279         try:
280             result = self['xep_0060'].retract(self._server, self.boundjid, data)
281             msg = ' Retracted item %s from topic %s' % (data, self.boundjid)
282             self.debug(msg)
283         except:
284             error = traceback.format_exc()
285             msg = 'Could not retract item %s from topic %s\ntraceback:\n%s' \
286                     % (data, self.boundjid, error)
287             self.error(msg)
288
289     def purge(self):
290         """  Purge the information in the server
291
292         """
293         try:
294             result = self['xep_0060'].purge(self._server, self.boundjid)
295             msg = ' Purged all items from topic %s' % self.boundjid
296             self.debug(msg)
297         except:
298             error = traceback.format_exc()
299             msg = ' Could not purge items from topic %s\ntraceback:\n%s' \
300                     % (self.boundjid, error)
301             self.error(msg)
302
303     def subscribe(self, node):
304         """ Subscribe to a topic
305
306         :param node: Name of the topic
307         :type node: str
308
309         """
310         try:
311             result = self['xep_0060'].subscribe(self._server, node)
312             msg = ' Subscribed %s to topic %s' \
313                     % (self.boundjid.user, node)
314             #self.info(msg)
315             self.debug(msg)
316         except:
317             error = traceback.format_exc()
318             msg = ' Could not subscribe %s to topic %s\ntraceback:\n%s' \
319                     % (self.boundjid.bare, node, error)
320             self.error(msg)
321
322     def unsubscribe(self, node):
323         """ Unsubscribe to a topic
324
325         :param node: Name of the topic
326         :type node: str
327
328         """
329         try:
330             result = self['xep_0060'].unsubscribe(self._server, node)
331             msg = ' Unsubscribed %s from topic %s' % (self.boundjid.bare, node)
332             self.debug(msg)
333         except:
334             error = traceback.format_exc()
335             msg = ' Could not unsubscribe %s from topic %s\ntraceback:\n%s' \
336                     % (self.boundjid.bare, node, error)
337             self.error(msg)
338
339     def check_mailbox(self, itype, attr):
340         """ Check the mail box
341
342         :param itype: type of mail
343         :type itype: str
344         :param attr: value wanted
345         :type attr: str
346
347         """
348         return self._parser.check_mailbox(itype, attr)
349
350
351     def handle_omf_message(self, iq):
352         """ Handle published/received message 
353
354         :param iq: Stanzas that is currently published/received
355         :type iq: Iq Stanza
356
357         """
358         self._parser.handle(iq)
359