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 652 2006-08-27 19:41:15Z 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