1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """Jabberd external component interface (jabber:component:accept).
19
20 Normative reference:
21 - `JEP 114 <http://www.jabber.org/jeps/jep-0114.html>`__
22 """
23
24 __docformat__="restructuredtext en"
25
26 import threading
27 import logging
28
29 from pyxmpp.jabberd.componentstream import ComponentStream
30 from pyxmpp.utils import from_utf8
31 from pyxmpp.jabber.disco import DiscoItems,DiscoInfo,DiscoIdentity
32 from pyxmpp.stanza import Stanza
33
35 """Jabber external component ("jabber:component:accept" protocol) interface
36 implementation.
37
38 Override this class to build your components.
39
40 :Ivariables:
41 - `jid`: component JID (should contain only the domain part).
42 - `secret`: the authentication secret.
43 - `server`: server to which the commonent will connect.
44 - `port`: port number on the server to which the commonent will
45 connect.
46 - `keepalive`: keepalive interval for the stream.
47 - `stream`: the XMPP stream object for the active connection
48 or `None` if no connection is active.
49 - `disco_items`: disco items announced by the component. Created
50 when a stream is connected.
51 - `disco_info`: disco info announced by the component. Created
52 when a stream is connected.
53 - `disco_identity`: disco identity (part of disco info) announced by
54 the component. Created when a stream is connected.
55 - `disco_category`: disco category to be used to create
56 `disco_identity`.
57 - `disco_type`: disco type to be used to create `disco_identity`.
58
59 :Types:
60 - `jid`: `pyxmpp.JID`
61 - `secret`: `unicode`
62 - `server`: `unicode`
63 - `port`: `int`
64 - `keepalive`: `int`
65 - `stream`: `pyxmpp.jabberd.ComponentStream`
66 - `disco_items`: `pyxmpp.jabber.DiscoItems`
67 - `disco_info`: `pyxmpp.jabber.DiscoInfo`
68 - `disco_identity`: `pyxmpp.jabber.DiscoIdentity`
69 - `disco_category`: `str`
70 - `disco_type`: `str`"""
71 - def __init__(self, jid=None, secret=None, server=None, port=5347,
72 disco_name=u"PyXMPP based component", disco_category=u"x-service",
73 disco_type=u"x-unknown", keepalive=0):
74 """Initialize a `Component` object.
75
76 :Parameters:
77 - `jid`: component JID (should contain only the domain part).
78 - `secret`: the authentication secret.
79 - `server`: server name or address the component should connect.
80 - `port`: port number on the server where the component should connect.
81 - `disco_name`: disco identity name to be used in the
82 disco#info responses.
83 - `disco_category`: disco identity category to be used in the
84 disco#info responses. Use `the categories registered by Jabber Registrar <http://www.jabber.org/registrar/disco-categories.html>`__
85 - `disco_type`: disco identity type to be used in the component's
86 disco#info responses. Use `the types registered by Jabber Registrar <http://www.jabber.org/registrar/disco-categories.html>`__
87 - `keepalive`: keepalive interval for the stream.
88
89 :Types:
90 - `jid`: `pyxmpp.JID`
91 - `secret`: `unicode`
92 - `server`: `str` or `unicode`
93 - `port`: `int`
94 - `disco_name`: `unicode`
95 - `disco_category`: `unicode`
96 - `disco_type`: `unicode`
97 - `keepalive`: `int`"""
98 self.jid=jid
99 self.secret=secret
100 self.server=server
101 self.port=port
102 self.keepalive=keepalive
103 self.stream=None
104 self.lock=threading.RLock()
105 self.state_changed=threading.Condition(self.lock)
106 self.stream_class=ComponentStream
107 self.disco_items=DiscoItems()
108 self.disco_info=DiscoInfo()
109 self.disco_identity=DiscoIdentity(self.disco_info,
110 disco_name, disco_category, disco_type)
111 self.register_feature("stringprep")
112 self.__logger=logging.getLogger("pyxmpp.jabberd.Component")
113
114
115
117 """Establish a connection with the server.
118
119 Set `self.stream` to the `pyxmpp.jabberd.ComponentStream` when
120 initial connection succeeds.
121
122 :raise ValueError: when some of the component properties
123 (`self.jid`, `self.secret`,`self.server` or `self.port`) are wrong."""
124 if not self.jid or self.jid.node or self.jid.resource:
125 raise ValueError,"Cannot connect: no or bad JID given"
126 if not self.secret:
127 raise ValueError,"Cannot connect: no secret given"
128 if not self.server:
129 raise ValueError,"Cannot connect: no server given"
130 if not self.port:
131 raise ValueError,"Cannot connect: no port given"
132
133 self.lock.acquire()
134 try:
135 stream=self.stream
136 self.stream=None
137 if stream:
138 stream.close()
139
140 self.__logger.debug("Creating component stream: %r" % (self.stream_class,))
141 stream=self.stream_class(jid = self.jid,
142 secret = self.secret,
143 server = self.server,
144 port = self.port,
145 keepalive = self.keepalive,
146 owner = self)
147 stream.process_stream_error=self.stream_error
148 self.stream_created(stream)
149 stream.state_change=self.__stream_state_change
150 stream.connect()
151 self.stream=stream
152 self.state_changed.notify()
153 self.state_changed.release()
154 except:
155 self.stream=None
156 self.state_changed.release()
157 raise
158
160 """Get the stream of the component in a safe way.
161
162 :return: Stream object for the component or `None` if no connection is
163 active.
164 :returntype: `pyxmpp.jabberd.ComponentStream`"""
165 self.lock.acquire()
166 stream=self.stream
167 self.lock.release()
168 return stream
169
175
177 """Get the socket of the connection to the server.
178
179 :return: the socket.
180 :returntype: `socket.socket`"""
181 return self.stream.socket
182
183 - def loop(self,timeout=1):
184 """Simple 'main loop' for a component.
185
186 This usually will be replaced by something more sophisticated. E.g.
187 handling of other input sources."""
188 self.stream.loop(timeout)
189
191 """Register a feature to be announced by Service Discovery.
192
193 :Parameters:
194 - `feature_name`: feature namespace or name.
195 :Types:
196 - `feature_name`: `unicode`"""
197 self.disco_info.add_feature(feature_name)
198
200 """Unregister a feature to be announced by Service Discovery.
201
202 :Parameters:
203 - `feature_name`: feature namespace or name.
204 :Types:
205 - `feature_name`: `unicode`"""
206 self.disco_info.remove_feature(feature_name)
207
208
209
211 """Handle various stream state changes and call right
212 methods of `self`.
213
214 :Parameters:
215 - `state`: state name.
216 - `arg`: state parameter.
217 :Types:
218 - `state`: `string`
219 - `arg`: any object"""
220 self.stream_state_changed(state,arg)
221 if state=="fully connected":
222 self.connected()
223 elif state=="authenticated":
224 self.authenticated()
225 elif state=="authorized":
226 self.authorized()
227 elif state=="disconnected":
228 self.state_changed.acquire()
229 try:
230 if self.stream:
231 self.stream.close()
232 self.stream_closed(self.stream)
233 self.stream=None
234 self.state_changed.notify()
235 finally:
236 self.state_changed.release()
237 self.disconnected()
238
264
290
291
293 """Do some "housekeeping" work like <iq/> result expiration. Should be
294 called on a regular basis, usually when the component is idle."""
295 stream=self.get_stream()
296 if stream:
297 stream.idle()
298
300 """Handle stream creation event.
301
302 [may be overriden in derived classes]
303
304 By default: do nothing.
305
306 :Parameters:
307 - `stream`: the stream just created.
308 :Types:
309 - `stream`: `pyxmpp.jabberd.ComponentStream`"""
310 pass
311
313 """Handle stream closure event.
314
315 [may be overriden in derived classes]
316
317 By default: do nothing.
318
319 :Parameters:
320 - `stream`: the stream just created.
321 :Types:
322 - `stream`: `pyxmpp.jabberd.ComponentStream`"""
323 pass
324
326 """Handle a stream error received.
327
328 [may be overriden in derived classes]
329
330 By default: just log it. The stream will be closed anyway.
331
332 :Parameters:
333 - `err`: the error element received.
334 :Types:
335 - `err`: `pyxmpp.error.StreamErrorNode`"""
336 self.__logger.debug("Stream error: condition: %s %r"
337 % (err.get_condition().name,err.serialize()))
338
340 """Handle a stream state change.
341
342 [may be overriden in derived classes]
343
344 By default: do nothing.
345
346 :Parameters:
347 - `state`: state name.
348 - `arg`: state parameter.
349 :Types:
350 - `state`: `string`
351 - `arg`: any object"""
352 pass
353
355 """Handle stream connection event.
356
357 [may be overriden in derived classes]
358
359 By default: do nothing."""
360 pass
361
363 """Handle successful authentication event.
364
365 A good place to register stanza handlers and disco features.
366
367 [should be overriden in derived classes]
368
369 By default: set disco#info and disco#items handlers."""
370 self.__logger.debug("Setting up Disco handlers...")
371 self.stream.set_iq_get_handler("query","http://jabber.org/protocol/disco#items",
372 self.__disco_items)
373 self.stream.set_iq_get_handler("query","http://jabber.org/protocol/disco#info",
374 self.__disco_info)
375
377 """Handle successful authorization event."""
378 pass
379
381 """Get disco#info data for a node.
382
383 [may be overriden in derived classes]
384
385 By default: return `self.disco_info` if no specific node name
386 is provided.
387
388 :Parameters:
389 - `node`: name of the node queried.
390 - `iq`: the stanza received.
391 :Types:
392 - `node`: `unicode`
393 - `iq`: `pyxmpp.Iq`"""
394 to=iq.get_to()
395 if to and to!=self.jid:
396 return iq.make_error_response("recipient-unavailable")
397 if not node and self.disco_info:
398 return self.disco_info
399 return None
400
402 """Get disco#items data for a node.
403
404 [may be overriden in derived classes]
405
406 By default: return `self.disco_items` if no specific node name
407 is provided.
408
409 :Parameters:
410 - `node`: name of the node queried.
411 - `iq`: the stanza received.
412 :Types:
413 - `node`: `unicode`
414 - `iq`: `pyxmpp.Iq`"""
415 to=iq.get_to()
416 if to and to!=self.jid:
417 return iq.make_error_response("recipient-unavailable")
418 if not node and self.disco_items:
419 return self.disco_items
420 return None
421
423 """Handle stream disconnection (connection closed by peer) event.
424
425 [may be overriden in derived classes]
426
427 By default: do nothing."""
428 pass
429
430
431