Changeset 143
- Timestamp:
- 05/06/02 23:04:15 (6 years ago)
- Files:
-
- trunk/RBJabber/RBJabber/Client.py (modified) (8 diffs)
- trunk/RBJabber/RBJabber/ClientNodes.py (added)
- trunk/RBJabber/RBJabber/Conference.py (modified) (5 diffs)
- trunk/RBJabber/RBJabber/JID.py (modified) (9 diffs)
- trunk/RBJabber/RBJabber/JabberConnection.py (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/RBJabber/RBJabber/Client.py
r140 r143 31 31 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 32 32 33 """Modular Jabber client 34 35 Dependencies: 36 Foundation.SubjectObserver 37 Foundation.Jabber 38 39 The Jabber protocol can be found at http://jabber.org, 40 and at the time of writting, specifically at 41 http://docs.jabber.org/general/html/protocol.html 42 43 """ 44 33 45 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 34 46 #~ Imports 35 47 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 36 48 37 import weakref38 49 from xml.sax.saxutils import escape, quoteattr 39 from Foundation import XMLObjectify, SubjectObserver 40 import JID 41 import Base 42 43 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 44 #~ Jabber Node Classes 45 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 46 47 class JabberNode(XMLObjectify.ObjectifiedXML): 48 def __init__(self, client, parent, namespace, node, attributes): 49 self._client = weakref.proxy(client) 50 XMLObjectify.ObjectifiedXML.__init__(self, client, parent, namespace, node, attributes) 51 52 def Content(self): 53 return [element[1] for element in self._elements if element[0][1] == ''] 54 55 def Elements(self, node=None, namespace=None): 56 elementList = self._elements 57 if namespace is not None: 58 elementList = [element for element in elementList if element[0][0] == namespace] 59 if node is None: elementList = [element[1] for element in elementList if element[0][1]] 60 elif node == '*': elementList = [element[1] for element in elementList] 61 else: elementList = [element[1] for element in elementList if element[0][1] == node] 62 return elementList 63 64 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 65 66 class JabberClientNode(JabberNode): 67 def _xmlInitComplete(self): 68 if self.__namespace__ == 'jabber:client': 69 self._client.stream.UpdateObserversEx({self.__node__: self}) 70 71 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 72 73 class JabberStream(JabberNode): 74 def _xmlInitStarted(self): 75 self._client.stream.UpdateObservers(settings=self) 76 def _addElement(self, namespace, node, element): pass 77 def _addData(self, data): pass 78 79 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 80 81 class JabberStreamError(JabberNode): 82 eJabberStreamError = 'JabberStreamError' 83 84 def _xmlInitComplete(self): 85 self._client.stream.UpdateObserversEx({self.__node__: self}) 86 self._client.Shutdown() 87 raise self.eJabberStreamError, ''.join(self._toXML()) 88 89 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 90 91 class JabberClientPresence(JabberClientNode): 92 _default_attributes = {'from':'', 'type':'available', 'id':'default_presence_id'} 93 94 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 95 96 class JabberClientMessage(JabberClientNode): 97 _default_attributes = {'from':'', 'to':'', 'type':'message', 'id':'default_message_id'} 98 99 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 100 101 class JabberClientIQ(JabberClientNode): 102 _default_attributes = {'from':'', 'type':'get', 'id':'default_iq_id'} 50 from Foundation.Jabber import Base 51 from Foundation.Jabber import ClientNodes 52 from Foundation.Jabber import JID 53 from Foundation import SubjectObserver 103 54 104 55 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ … … 106 57 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 107 58 108 class Client(Base.Client): 109 DefaultJabberNode = JabberNode 59 class Client(Base.BasicClient): 60 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 61 #~ Constants / Variables / Etc. 62 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 63 110 64 JabberNodeMap = { 111 ('http://etherx.jabber.org/streams', 'stream'): JabberStream, 112 ('http://etherx.jabber.org/streams', 'error'): JabberStreamError, 113 ('jabber:client', 'presence'): JabberClientPresence, 114 ('jabber:client', 'message'): JabberClientMessage, 115 ('jabber:client', 'iq'): JabberClientIQ, 65 # http://etherx.jabber.org/streams nodes 66 ('http://etherx.jabber.org/streams', 'stream'): ClientNodes.JabberStream, 67 ('http://etherx.jabber.org/streams', 'error'): ClientNodes.JabberStreamError, 68 69 # jabber:client nodes 70 ('jabber:client', 'presence'): ClientNodes.JabberClientPresence, 71 ('jabber:client', 'message'): ClientNodes.JabberClientMessage, 72 ('jabber:client', 'iq'): ClientNodes.JabberClientIQ, 73 74 None: ClientNodes.JabberNode, # Default if no match with a defined (namespace, node) 116 75 } 117 76 118 77 stream = None 78 information = None 119 79 SupportedNS = None 80 81 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 82 #~ Special 83 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 120 84 121 85 def __init__(self, *args, **kw): 122 86 self.stream = SubjectObserver.BidableCategorySubject() 123 87 self.information = SubjectObserver.AttributedSubject.AttributedSubject() 124 apply(Base. Client.__init__, (self,) + args, kw)88 apply(Base.BasicClient.__init__, (self,) + args, kw) 125 89 126 90 self.SupportedNS = [] 127 91 self.__NextID = 0 128 92 93 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 94 #~ Protected Methods 95 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 96 129 97 def _GetNextID(self): 98 """Used by various methods to obtain a "new id" for a message.""" 130 99 self.__NextID += 1 131 100 return str(self.__NextID) 132 101 102 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 103 #~ Public Methods 104 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 105 133 106 def Authenticate(self, username, password, resource, callback=None): 107 """Authenticates with the Jabber server. 108 Assumes basic authentication protocol. 109 110 Returns the iqAuthQuery instance that is sent. 111 112 See also: iqAuthQuery""" 134 113 import iqAuthQuery 135 114 query = iqAuthQuery.iqAuthQuery(self, username, password, resource, callback) … … 137 116 138 117 def Register(self, username, password, callback=None): 118 """Registers a new account with the Jabber server. 119 Assumes basic registration protocol. 120 121 Returns the iqQuery instance that is sent. 122 123 See also: iqQuery""" 139 124 import iqQuery 140 125 strXML = '<username>%s</username><password>%s</password>' % (username, password) … … 144 129 145 130 def QueryRoster(self, callback=None): 131 """Requests the jabber:iq:roster for the currently authenticated JID. 132 133 Returns the iqRosterQuery instance that is sent. 134 135 See also: iqRosterQuery""" 146 136 import iqRosterQuery 147 137 return iqRosterQuery.iqRosterQuery(self, callback) 148 138 149 139 def BrowseJID(self, toJID, callback): 140 """Requests the jabber:iq:browse data for the currently authenticated JID. 141 142 Returns the iqQuery instance that is sent. 143 144 See also: iqQuery""" 150 145 import iqQuery 151 146 query = iqQuery.iqQuery(self, callback) … … 153 148 return query 154 149 150 def SetPublicData(self, namespace, xml): 151 """Saves xml data into namespace. 152 153 Returns the sent XML.""" 154 return self.SendXML('''<iq id='%s' type='set'><query xmlns='%s'>%s</query></iq> ''' % (self._GetNextID(), namespace, xml)) 155 SetData = SetPublicData # Alias 156 155 157 def SetPrivateData(self, xml): 158 """Saves xml data into the jabber:iq:private. 159 160 Returns the sent XML.""" 156 161 return self.SetData('jabber:iq:private', xml) 157 162 158 def SetData(self, namespace, xml): 159 return self.SendXML('''<iq id='%s' type='set'><query xmlns='%s'>%s</query></iq> ''' % (self._GetNextID(), namespace, xml)) 160 163 def GetPublicData(self, namespace, callback=None, xml=''): 164 """Requests the data stored in namespace. 165 166 Returns the iqQuery instance that is sent. 167 168 See also: iqQuery""" 169 return self.Query(namespace, '', type='get', callback=callback, xml=xml) 170 GetData = GetPublicData # Alias 171 161 172 def GetPrivateData(self, callback=None, xml=''): 173 """Requests the data stored in jabber:iq:private. 174 175 Returns the iqQuery instance that is sent. 176 177 See also: iqQuery""" 162 178 return self.GetData('jabber:iq:private', callback=callback, xml=xml) 163 179 164 def GetData(self, namespace, callback=None, xml=''):165 return self.Query(namespace, '', type='get', callback=callback, xml=xml)166 167 180 def Query(self, namespace, toJID='', type='get', callback=None, xml=''): 181 """Sends a jabber:client iq query to toJID. 182 183 Returns the iqQuery instance that is sent. 184 185 See also: iqQuery""" 168 186 import iqQuery 169 187 query = iqQuery.iqQuery(self, callback) … … 172 190 173 191 def Message(self, toJID, body='', subject='', type='message', id=None, xml=''): 192 """Sends a jabber:client message to toJID. 193 194 Returns the sent XML.""" 174 195 idMessage = id or self._GetNextID() 175 196 strXML = '<message id=%s to=%s type=%s>' % (quoteattr(idMessage), quoteattr(toJID), quoteattr(type)) … … 182 203 return self.SendXML(strXML) 183 204 184 def Subscribe(self, toJID='', type='subscribe'):185 return self.Presence(toJID=toJID, type=type)186 187 def Unsubscribe(self, toJID='', type='unsubscribe'):188 return self.Presence(toJID=toJID, type=type)189 190 205 def Presence(self, toJID='', status='', show='', type='available', xml=''): 206 """Sends a jabber:client presence to toJID or everyone if not toJID. 207 208 Returns the sent XML.""" 191 209 idPresence = self._GetNextID() 192 210 strXML = '<presence id=%s ' % quoteattr(idPresence) … … 195 213 strXML += '>' 196 214 197 if show: 198 strXML += '<show>%s</show>' % escape(show) 199 if status: 200 strXML += '<status>%s</status>' % escape(status) 201 if xml: 202 strXML += xml 215 if show: strXML += '<show>%s</show>' % escape(show) 216 if status: strXML += '<status>%s</status>' % escape(status) 217 if xml: strXML += xml 203 218 strXML += '</presence>' 204 219 return self.SendXML(strXML) 205 220 206 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 207 #~ Testing 208 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 209 210 if __name__=='__main__': 211 import Test 212 221 def Subscribe(self, toJID, type='subscribe'): 222 """Sends a jabber:client presence subscribe request to toJID. 223 224 Returns the sent XML.""" 225 return self.Presence(toJID=toJID, type=type) 226 227 def Unsubscribe(self, toJID, type='unsubscribe'): 228 """Sends a jabber:client presence unsubscribe request to toJID. 229 230 Returns the sent XML.""" 231 return self.Presence(toJID=toJID, type=type) 232 233 trunk/RBJabber/RBJabber/Conference.py
r138 r143 31 31 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 32 32 33 """Conference implementation for Jabber conferencing 34 35 The Jabber protocol can be found at http://jabber.org, 36 and at the time of writting, specifically at 37 http://docs.jabber.org/general/html/protocol.html 38 39 Specifically, this module relates to jabber:iq:conference. 40 """ 41 33 42 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 34 43 #~ Imports … … 45 54 46 55 class Conference(JabberSubjectBase): 56 """Sets up a Jabber conference at a specific conference JID. 57 58 Note that conferencing is a Jabber module, and as such, needs to 59 be enabled on the Jabber server itself. 60 """ 61 62 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 63 #~ Special 64 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 65 47 66 def __init__(self, JC, ConferenceJID): 48 67 JabberSubjectBase.__init__(self) … … 62 81 63 82 def __del__(self): 64 if self.JC(): 65 self.Presence(type='unavailable') 83 """Disconnects from the conference if the Jabber Client still exists""" 84 if self.JC(): self.Presence(type='unavailable') 85 86 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 87 #~ Protected Methods 88 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 66 89 67 90 def _CallConference(self, subject, **UpdateDict): 68 pass 91 """Dummy method to place in the stream Observer. Should never be called.""" 92 assert 0 69 93 70 94 def _BidConference(self, subject, **UpdateDict): 95 """Selects only the messages off the jabber stream that are pertinate to the conference JID""" 71 96 for each in UpdateDict.itervalues(): 72 97 if each.from_ not in self.ConferenceJID: … … 75 100 return apply(self.__super.Bid, (subject,), UpdateDict) 76 101 77 def Ask(self, callback): 78 self.JC().Query('jabber:iq:conference', toJID=self.ConferenceJID, type='get', callback=callback) 102 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 103 #~ Public Methods 104 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 79 105 80 def JoinAs(self, NickName, Secret='', callback=None): 81 if not isinstance(NickName, (list, tuple)): NickName = (NickName,) 82 xmlNickName = ''.join(['<nick>%s</nick>' % x for x in NickName]) 106 def Disconnect(self): 107 """Disconnects from the conference by sending and unavailable presence""" 108 return self.Presence(type='unavailable') 109 110 def GetInformation(self, callback): 111 """Requests information about the conference in question. 112 113 Returns iqQuery object.""" 114 return self.JC().Query('jabber:iq:conference', toJID=self.ConferenceJID, type='get', callback=callback) 115 116 def JoinAs(self, Nick, Secret='', callback=None): 117 """Joins the conference using Nick(s), and Secret if need be. 118 119 Returns iqQuery object.""" 120 if not isinstance(Nick, (list, tuple)): Nick = (Nick,) 121 xmlNick = ''.join(['<nick>%s</nick>' % x for x in Nick]) 83 122 xmlSecret = Secret and ('<secret>%s</secret>' % Secret) or '' 84 self. JC().Presence(toJID=self.ConferenceJID)85 self.JC().Query('jabber:iq:conference', toJID=self.ConferenceJID, type='set', xml=xmlNickName+ xmlSecret, callback=callback)123 self.Presence() 124 return self.JC().Query('jabber:iq:conference', toJID=self.ConferenceJID, type='set', xml=xmlNick + xmlSecret, callback=callback) 86 125 126 def ChangeNick(self, Nick, callback=None): 127 """Changes the nickname of the conference connection""" 128 if not isinstance(Nick, (list, tuple)): Nick = (Nick,) 129 xmlNick = ''.join(['<nick>%s</nick>' % x for x in Nick]) 130 return self.JC().Query('jabber:iq:conference', toJID=self.ConferenceJID, type='set', xml=xmlNick, callback=callback) 131 87 132 def Invite(self, *args, **kw): 133 """Invites a JID to join the conference""" 88 134 kw['xml'] = kw.get('xml', '') + '<x xmlns="jabber:x:conference" jid="%s"/>' % self.ConferenceJID 89 135 return apply(self.JC().Message, args, kw) 90 136 91 137 def Message(self, *args, **kw): 138 """Sends a message to the conference""" 92 139 return apply(self.JC().Message, (self.ConferenceJID, ) + args, kw) 93 140 94 141 def Presence(self, *args, **kw): 142 """Sends a presence to the conference""" 95 143 return apply(self.JC().Presence, (self.ConferenceJID, ) + args, kw) 96 144 … … 114 162 conf.Message('Hello from RB development!!!') 115 163 jc.ProcessPending(1.0) 116 try: 164 try: 117 165 while 1: jc.Process(1.0) 118 except KeyboardInterrupt: 119 pass 120 #del conf 121 #try: 122 # while 1: jc.Process(1.0) 123 #except KeyboardInterrupt: 124 # pass 166 except KeyboardInterrupt: pass 125 167 print "Test complete." 126 168 trunk/RBJabber/RBJabber/JID.py
r108 r143 31 31 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 32 32 33 """JID class and convenience functions. 34 35 Dependencies: 36 re 37 """ 38 33 39 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 34 40 #~ Imports … … 40 46 #~ Constants / Variables / Etc. 41 47 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 49 __all__ = ['reJabberURL', 'reJabberJID', 'JID'] 42 50 43 51 _restrID = '''[\w-]+(?:[\w\.-]+[\w-]+)?''' … … 47 55 reJabberJID = re.compile('''(?:(%s)@)?(%s)(?:/(%s))?''' % (_restrID, _restrServer, _restrResource)) 48 56 49 __all__ = ['reJabberURL', 'reJabberJID']50 51 57 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 52 58 #~ Definitions … … 54 60 55 61 def splitnorm(strJID, start=1, end=4): 62 """Splits username@server/resource into [username, server, resource], 63 and normalizes username and server. Returns a start to end-1 of the list""" 56 64 result = reJabberJID.split(strJID)[start:end] 57 65 return [x and x.lower() or x for x in result[1-start:3-start]] + result[3-start:] 58 66 59 67 def split(strJID, start=1, end=4): 68 """Splits username@server/resource into [username, server, resource]. 69 Returns a start to end-1 of the list""" 60 70 return reJabberJID.split(strJID)[start:end] 61 71 62 72 def splitex(strJID, start=1, end=4, normalize=0): 73 """Uses normalize flag to choose between split and splitnorm.""" 63 74 if normalize: return splitnorm(strJID, start, end) 64 75 else: return split(strJID, start, end) 65 76 66 77 def cmp_(strJIDa, strJIDb, resource=1): 78 """Compares two JIDs, including resources if flag is set. 79 Note that case is insensitive in username and server, but IS sensitive in the resource.""" 67 80 jida = splitnorm(strJIDa, end=3+resource) 68 81 jidb = splitnorm(strJIDb, end=3+resource) … … 70 83 71 84 def compare(*args, **kw): 85 """Same as cmp_, except returns a python truth value instead of a cmp result.""" 72 86 return 0 == apply(cmp_, args, kw) 73 87 74 88 def contains(strJIDa, strJIDb): 89 """Returns true if strJIDb is strJIDb, or strJIDb is qualified by a resource. 90 Will fail if strJIDa has a resource and is not the same JID as strJIDb.""" 75 91 jida = filter(None, splitnorm(strJIDa, end=4)) 76 92 jidb = filter(None, splitnorm(strJIDb, end=3))[-len(jida):] … … 78 94 79 95 def join(*args): 96 """Combines a (username, server, resource) into username@server/resource, but does so 97 in several different formats""" 80 98 if isinstance(args[0], (tuple, list)): 81 99 args = tuple(args[0]) … … 89 107 else: return '' 90 108 91 def username(strJID, normalize=0): return splitex(strJID, 1, 2, normalize)[0] 92 def server(strJID, normalize=0): return splitex(strJID, 2, 3, normalize)[0] 93 def resource(strJID, normalize=0): return splitex(strJID, 3, 4, normalize)[0] 94 def noresource(strJID, normalize=0): return join(splitex(strJID, 1, 3, normalize)) 95 def nominal(strJID): return join(splitnorm(strJID, 1, 3)) 96 def normalize(strJID): return join(splitnorm(strJID)) 109 def username(strJID, normalize=0): 110 """Extracts the username from the JID""" 111 return splitex(strJID, 1, 2, normalize)[0] 112 def server(strJID, normalize=0): 113 """Extracts the server from the JID""" 114 return splitex(strJID, 2, 3, normalize)[0] 115 def resource(strJID, normalize=0): 116 """Extracts the resource from the JID""" 117 return splitex(strJID, 3, 4, normalize)[0] 118 def noresource(strJID, normalize=0): 119 """Returns the JID without the resource qualification""" 120 return join(splitex(strJID, 1, 3, normalize)) 121 def nominal(strJID): 122 """Returns the JID with username and server lowercased, and no resource""" 123 return join(splitnorm(strJID, 1, 3)) 124 def normalize(strJID): 125 """Returns the JID with the username and server lowercased, but retaining the resource""" 126 return join(splitnorm(strJID)) 97 127 98 128 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ … … 102 132 class JID(str): 103 133 """An object-oriented way of dealing with JIDs""" 134 135 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 136 #~ Public Methods 137 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 138 104 139 split = split 105 140 splitnorm = splitnorm … … 118 153 return Class(apply(join, args)) 119 154 join = classmethod(join) 155 156 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 157 #~ Special 158 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 120 159 121 160 __cmp__ = cmp_ trunk/RBJabber/RBJabber/JabberConnection.py
r129 r143 31 31 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 32 32 33 """Very basic Jabber client. 34 35 Dependencies: 36 Foundation.SmartSelect 37 Foundation.XMLBuilder 38 socket 39 40 The Jabber protocol can be found at http://jabber.org, 41 and at the time of writting, specifically at 42 http://docs.jabber.org/general/html/protocol.html 43 44 """ 45 33 46 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 34 47 #~ Imports … … 39 52 from Foundation.ContextApply import BindCallable 40 53 import JID 41 from JID import reJabberURL, reJabberJID42 54 import socket 43 55 import select … … 54 66 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 55 67 56 class Client(XMLBuilderMixin, SmartSelect.SmartSelectClientBase): 68 class BasicClient(XMLBuilderMixin, SmartSelect.SmartSelectClientBase): 69 """Very basic Jabber client, including: 70 - Smart select socket handling, 71 - Structured XML parsing, 72 - Initializing and finalizing connection with the Jabber Server 73 """ 74 57 75 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 58 76 #~ Constants / Variables / Etc. … … 60 78 61 79 ServerJID = None 62 DefaultJabberNode = XMLBuilderObjectBase 63 JabberNodeMap = {} 80 JabberNodeMap = {None: XMLBuilderObjectBase} 64 81 65 82 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ … … 76 93 self._sendData = '' 77 94 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 78 params = reJabberURL.split(JabberServerURL)[1:-1]95 params = JID.reJabberURL.split(JabberServerURL)[1:-1] 79 96 self._socket.connect((params[0], port)) 80 97 self.fileno = self._socket.fileno … … 95 112 96 113 def BuildJID(self, username='', server='', resource=''): 114 """Returns a JID that will resolve to username@server/resource. 115 Additionally, if you simply want the corresponding JID on the current 116 Jabber Server, all you need to pass is the username.""" 97 117 return JID.JID.join(username, server or self.ServerJID, resource) 98 118 99 def SendXML(self, xmlData): 100 self._sendData += xmlData 101 return xmlData 119 def SendXML(self, xml): 120 """Sends bare-bones xml data on the socket. 121 Note: the xml is not checked for validity, but the Jabber Servers get 122 pretty upset when the data is invalid, and they tend to sever the socket.""" 123 self._sendData += xml 124 return xml 102 125 103 126 def Shutdown(self): 127 """Politely disconnects the socket and parser from the jabber stream""" 104 128 if __debug__: print 'Disconnecting %s from %s' % (self.__class__.__name__, self.ServerJID) 105 129 self._SendXMLImmediate('%s' % _xmlJabberFooter) … … 109 133 110 134 def _SetDebug(self, fileIn, fileOut): 135 """Sets the debug input and output files (or file-like-objects). 136 These can also be passed in when creating the Jabber Client""" 111 137 self._fileIn = fileIn 112 138 self._fileOut = fileOut … … 117 143 118 144 def _start_element(self, name, attributes): 145 """Used internally by the XML parsing mechanism. 146 This override replaces jid-related attributes with a JID class instance.""" 119 147 if name[:7] == 'jabber:': 120 148 for key in ('to', 'from', 'jid'): 121 149 if key in attributes: 122 150 attributes[key] = JID.JID(attributes[key]) 123 return s uper(Client, self)._start_element(name, attributes)151 return self.__super._start_element(name, attributes) 124 152 125 153 def _GetElementFactory(self, owner, parent, namespace, node, attributes): 126 result = None 127 128 if self._elements: 129 # Try to get a factory from the parent 130 result = self._elements[-1]._xmlChildFactory(owner, parent, namespace, node, attributes) 131 132 if not result: 133 # Otherwise, try to load it from the disk 134 result = self.JabberNodeMap.get((namespace, node), self.DefaultJabberNode) 135 136 return result 154 """Again used by the XML parsing mechanism to find an Element class factory to 155 represent the node defined by (owner, parent, namespace, node, attributes).""" 156 return self.JabberNodeMap.get((namespace, node), self.JabberNodeMap[None]) 137 157 138 158 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ … … 140 160 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 141 161 142 def _NeedsRead(self): return self._socket and 1 162 def _NeedsRead(self): 163 """Used by the SmartSelect mechanism to signal when reading is needed.""" 164 return self._socket and 1 143 165 def _ProcessRead(self): 166 """Called by the SmartSelect mechanism when the socket is ready to be read from.""" 144 167 data = self._socket.recv(8192) 145 168 if self._fileIn: print >> self._fileIn, data 146 169 self._parser.Parse(data) 147 170 148 def _NeedsWrite(self): return self._socket and self._sendData and 1 or 0 171 def _NeedsWrite(self): 172 """Used by the SmartSelect mechanism to signal when writing is needed.""" 173 return self._socket and self._sendData and 1 or 0 149 174 def _ProcessWrite(self): 175 """Called by the SmartSelect mechanism when the socket is ready to be written to.""" 150 176 nSent = self._socket.send(self._sendData) 151 177 if self._fileOut: print >> self._fileOut, self._sendData[:nSent] … … 156 182 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 157 183 158 def _SendXMLImmediate(self, xmlData): 159 self._sendData += xmlData 160 while self._sendData: 184 def _SendXMLImmediate(self, xml): 185 """Similar to SendXML, except that _SendXMLImmediate only returns when all the pending 186 XML data has been written to the socket.""" 187 self._sendData += xml 188 while self._sendData and select.select([], [self._socket], []): 161 189 nSent = self._socket.send(self._sendData) 162 190 if self._fileOut: print >> self._fileOut, self._sendData[:nSent] 163 191 self._sendData = self._sendData[nSent:] 164 192 193 # Keep the super around for speed 194 BasicClient._BasicClient__super = super(BasicClient)
