root/trunk/RBJabber/RBJabber/JabberConnection.py

Revision 756, 8.8 kB (checked in by sholloway, 4 years ago)

Revised the inheritence from RBFoundation changes

Line 
1 #!/usr/bin/env python
2 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 ##~ License
4 ##~
5 ##- The RuneBlade Foundation library is intended to ease some
6 ##- aspects of writing intricate Jabber, XML, and User Interface (wxPython, etc.)
7 ##- applications, while providing the flexibility to modularly change the
8 ##- architecture. Enjoy.
9 ##~
10 ##~ Copyright (C) 2002  TechGame Networks, LLC.
11 ##~
12 ##~ This library is free software; you can redistribute it and/or
13 ##~ modify it under the terms of the BSD style License as found in the
14 ##~ LICENSE file included with this distribution.
15 ##~
16 ##~ TechGame Networks, LLC can be reached at:
17 ##~ 3578 E. Hartsel Drive #211
18 ##~ Colorado Springs, Colorado, USA, 80920
19 ##~
20 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21
22 """Very basic Jabber client.
23
24 Dependencies:
25     RBFoundation.SmartSelect
26     RBFoundation.XMLBuilder
27     socket
28
29 The Jabber protocol can be found at http://jabber.org,
30 and at the time of writting, specifically at
31 http://docs.jabber.org/general/html/protocol.html
32
33 """
34
35 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36 #~ Imports                                           
37 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
38
39 from RBFoundation.XMLClassBuilder import XMLClassBuilder
40 from RBFoundation import SmartSelect
41 from RBFoundation.BindCallable import BindCallable
42 import JID
43 import sys
44 import socket
45 import select
46
47 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48 #~ Constants / Variables / Etc.
49 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
50
51 _xmlJabberHeader = '<?xml version="1.0" encoding="UTF-8" ?><stream:stream to="%s" xmlns="%s" xmlns:stream="http://etherx.jabber.org/streams">'
52 _xmlJabberFooter = '</stream:stream>'
53
54
55 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56 #~ Classes
57 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
58
59 class JabberConnection(XMLClassBuilder, SmartSelect.SmartSelectClientBase):
60     """Very basic Jabber client, including:
61         - Smart select socket handling,
62         - Structured XML parsing,
63         - Initializing and finalizing connection with the Jabber Server
64     """
65
66     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
67     #~ Constants / Variables / Etc.
68     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69
70     _socket = None
71     _parser = None
72     ServerJID = None
73
74     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
75     #~ Special
76     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
77
78     def __init__(self, *args, **kw):
79         # Base code
80         XMLClassBuilder.__init__(self)
81
82         if args or kw:
83             self.Startup(*args, **kw)
84
85     def __del__(self):
86         if self: self.Shutdown()
87
88     def __nonzero__(self):
89         return self._socket and 1 or 0
90
91     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
92     #~ Public Methods
93     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
94
95     def Startup(self, JabberServerURL, port=5222, ServerJID=None, namespace='jabber:client', fileIn=None, fileOut=None, **kw):
96         """Creates a Socket and XML parser to handle connection to a Jabber server.
97
98         JabberServerURL & port specify how the socket is connected to Jabber server,
99         ServerJID is to be set if the ServerJID is different than the JabberServerURL hostname, and
100         fileIn and fileOut should be file-like-objects to receive the influx and outflux streams.
101
102         """
103         self._SetDebug(fileIn, fileOut)
104
105         # Create the sendData buffer and lock
106         try:
107             self._sendData = ''
108             import threading
109             self._sendDataLock = threading.Lock()
110         except ImportError:
111             self._sendDataLock = None
112
113         # Create our socket
114         try:
115             self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
116             params = JID.reJabberURL.split(JabberServerURL)[1:-1]
117             self._socket.connect((params[0], port))
118         except socket.error:
119             self._SetSocketError(*sys.exc_info())
120             raise
121         self.fileno = self._socket.fileno
122        
123         self.ServerJID = JID.JID(ServerJID or params[0])
124
125         # Create our xml parser
126         self._parser = self._CreateParser()
127
128         # Send the initial header
129         self.SendXML(_xmlJabberHeader % (self.ServerJID, namespace))
130
131     def Shutdown(self):
132         """Politely disconnects the socket and parser from the jabber stream"""
133         if __debug__: print 'Disconnecting %s from %s' % (self.__class__.__name__, self.ServerJID)
134         if self._socket:
135             try: self._SendXMLImmediate('%s' % _xmlJabberFooter)
136             except socket.error: pass
137         if self._fileIn: print >> self._fileIn, _xmlJabberFooter
138         self._socket = None
139         self._parser = None
140
141     def _SetDebug(self, fileIn, fileOut):
142         """Sets the debug input and output files (or file-like-objects). 
143         These can also be passed in when creating the Jabber Client"""
144         self._fileIn = fileIn
145         self._fileOut = fileOut
146
147     def BuildJID(self, username='', server='', resource=''):
148         """Returns a JID that will resolve to username@server/resource. 
149         Additionally, if you simply want the corresponding JID on the current
150         Jabber Server, all you need to pass is the username."""
151         return JID.JID.join(username, server or self.ServerJID, resource)
152        
153     def SendXML(self, xml):
154         """Sends bare-bones xml data on the socket. 
155         Note: the xml is not checked for validity, but the Jabber Servers get
156         pretty upset when the data is invalid, and they tend to sever the socket."""
157         try:
158             if self._sendDataLock: self._sendDataLock.acquire()
159             self._sendData += xml
160         finally:
161             if self._sendDataLock: self._sendDataLock.release()
162         return xml
163
164     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
165     #~ XMLBuilding Private Methods
166     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
167
168     def _start_element(self, name, attributes):
169         """Used internally by the XML parsing mechanism. 
170         This override replaces jid-related attributes with a JID class instance."""
171         if name[:7] == 'jabber:':
172             for key in ('to', 'from', 'jid'):
173                 if key in attributes:
174                     attributes[key] = JID.JID(attributes[key])
175         return self.__super._start_element(name, attributes)
176
177     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
178     #~ Smart Select Private Methods
179     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
180
181     def _NeedsRead(self):
182         """Used by the SmartSelect mechanism to signal when reading is needed."""
183         return self._socket and 1
184     def _ProcessRead(self):
185         """Called by the SmartSelect mechanism when the socket is ready to be read from."""
186         data = self._SocketRecv()
187         if self._fileIn: print >> self._fileIn, data
188         self._parser.Parse(data)
189          
190     def _NeedsWrite(self):
191         """Used by the SmartSelect mechanism to signal when writing is needed."""
192         return self._socket and self._sendData and 1 or 0
193     def _ProcessWrite(self):
194         """Called by the SmartSelect mechanism when the socket is ready to be written to."""
195         try:
196             if self._sendDataLock: self._sendDataLock.acquire()
197             nSent = self._SocketSend(self._sendData)
198             if self._fileOut: print >> self._fileOut, self._sendData[:nSent]
199             self._sendData = self._sendData[nSent:]
200         finally:
201             if self._sendDataLock: self._sendDataLock.release()
202
203     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
204     #~ Other Private Methods
205     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
206
207     def _SendXMLImmediate(self, xml):
208         """Similar to SendXML, except that _SendXMLImmediate only returns when all the pending
209         XML data has been written to the socket."""
210         try:
211             if self._sendDataLock: self._sendDataLock.acquire()
212             self._sendData += xml
213             while self._sendData and select.select([], [self._socket], [], 0.5):
214                 nSent = self._SocketSend(self._sendData)
215                 if self._fileOut: print >> self._fileOut, self._sendData[:nSent]
216                 self._sendData = self._sendData[nSent:]
217         finally:
218             if self._sendDataLock: self._sendDataLock.release()
219
220     def _SocketRecv(self, limit=8192):
221         try:
222             return self._socket.recv(limit)
223         except socket.error:
224             self._SetSocketError(*sys.exc_info())
225             raise
226
227     def _SocketSend(self, data):
228         try:
229             return self._socket.send(data)
230         except socket.error:
231             self._SetSocketError(*sys.exc_info())
232             raise
233
234     def _SetSocketError(self, exc_class, exc_info, exc_traceback):
235         del exc_class
236         del exc_info
237         del exc_traceback
238
239 # Keep the super around for speed
240 JabberConnection._JabberConnection__super = super(JabberConnection)
Note: See TracBrowser for help on using the browser.