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