Changeset 128:db5de9ee45fa for wokkel/disco.py
- Timestamp:
- Jun 4, 2009, 10:15:16 AM (13 years ago)
- Branch:
- wokkel-muc-client-support-24
- Parents:
- 127:1610e7a3b777 (diff), 60:e5e6ff37ea27 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent. - Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
wokkel/disco.py
r49 r128 409 409 410 410 411 class DiscoRequest(xmlstream.IQ): 412 """ 413 Disco request. 414 415 @ivar namespace: Request namespace. 416 @type namespace: C{str} 417 @ivar method: Type attribute of the IQ request. Either C{'set'} or C{'get'} 418 @type method: C{str} 419 """ 420 421 def __init__(self, xs, namespace=NS, method='get'): 422 xmlstream.IQ.__init__(self, xs, method) 423 self.addElement((namespace, 'query')) 424 425 426 411 427 class DiscoHandler(XMPPHandler, IQHandlerMixin): 412 428 """ -
wokkel/disco.py
r108 r128 1 1 # -*- test-case-name: wokkel.test.test_disco -*- 2 2 # 3 # Copyright (c) 2003-200 8Ralph Meijer3 # Copyright (c) 2003-2009 Ralph Meijer 4 4 # See LICENSE for details. 5 5 … … 15 15 from twisted.words.xish import domish 16 16 17 from wokkel import data_form 17 18 from wokkel.iwokkel import IDisco 18 19 from wokkel.subprotocols import IQHandlerMixin, XMPPHandler 19 20 20 NS = 'http://jabber.org/protocol/disco'21 NS_ INFO = NS+ '#info'22 NS_ ITEMS = NS+ '#items'21 NS_DISCO = 'http://jabber.org/protocol/disco' 22 NS_DISCO_INFO = NS_DISCO + '#info' 23 NS_DISCO_ITEMS = NS_DISCO + '#items' 23 24 24 25 IQ_GET = '/iq[@type="get"]' 25 DISCO_INFO = IQ_GET + '/query[@xmlns="' + NS_INFO + '"]' 26 DISCO_ITEMS = IQ_GET + '/query[@xmlns="' + NS_ITEMS + '"]' 27 28 class DiscoFeature(domish.Element): 29 """ 30 Element representing an XMPP service discovery feature. 31 """ 32 33 def __init__(self, feature): 34 domish.Element.__init__(self, (NS_INFO, 'feature'), 35 attribs={'var': feature}) 36 37 38 class DiscoIdentity(domish.Element): 39 """ 40 Element representing an XMPP service discovery identity. 41 """ 42 43 def __init__(self, category, type, name = None): 44 domish.Element.__init__(self, (NS_INFO, 'identity'), 45 attribs={'category': category, 46 'type': type}) 47 if name: 48 self['name'] = name 49 50 51 class DiscoItem(domish.Element): 52 """ 53 Element representing an XMPP service discovery item. 54 """ 55 56 def __init__(self, jid, node='', name=None): 57 domish.Element.__init__(self, (NS_ITEMS, 'item'), 58 attribs={'jid': jid.full()}) 59 if node: 60 self['node'] = node 61 62 if name: 63 self['name'] = name 26 DISCO_INFO = IQ_GET + '/query[@xmlns="' + NS_DISCO_INFO + '"]' 27 DISCO_ITEMS = IQ_GET + '/query[@xmlns="' + NS_DISCO_ITEMS + '"]' 28 29 class DiscoFeature(unicode): 30 """ 31 XMPP service discovery feature. 32 33 This extends C{unicode} to convert to and from L{domish.Element}, but 34 further behaves identically. 35 """ 36 37 def toElement(self): 38 """ 39 Render to a DOM representation. 40 41 @rtype: L{domish.Element}. 42 """ 43 element = domish.Element((NS_DISCO_INFO, 'feature')) 44 element['var'] = unicode(self) 45 return element 46 47 48 @staticmethod 49 def fromElement(element): 50 """ 51 Parse a DOM representation into a L{DiscoFeature} instance. 52 53 @param element: Element that represents the disco feature. 54 @type element: L{domish.Element}. 55 @rtype L{DiscoFeature}. 56 """ 57 featureURI = element.getAttribute('var', u'') 58 feature = DiscoFeature(featureURI) 59 return feature 60 61 62 63 class DiscoIdentity(object): 64 """ 65 XMPP service discovery identity. 66 67 @ivar category: The identity category. 68 @type category: C{unicode} 69 @ivar type: The identity type. 70 @type type: C{unicode} 71 @ivar name: The optional natural language name for this entity. 72 @type name: C{unicode} 73 """ 74 75 def __init__(self, category, idType, name=None): 76 self.category = category 77 self.type = idType 78 self.name = name 79 80 81 def toElement(self): 82 """ 83 Generate a DOM representation. 84 85 @rtype: L{domish.Element}. 86 """ 87 element = domish.Element((NS_DISCO_INFO, 'identity')) 88 if self.category: 89 element['category'] = self.category 90 if self.type: 91 element['type'] = self.type 92 if self.name: 93 element['name'] = self.name 94 return element 95 96 97 @staticmethod 98 def fromElement(element): 99 """ 100 Parse a DOM representation into a L{DiscoIdentity} instance. 101 102 @param element: Element that represents the disco identity. 103 @type element: L{domish.Element}. 104 @rtype L{DiscoIdentity}. 105 """ 106 category = element.getAttribute('category') 107 idType = element.getAttribute('type') 108 name = element.getAttribute('name') 109 feature = DiscoIdentity(category, idType, name) 110 return feature 111 112 113 114 class DiscoInfo(object): 115 """ 116 XMPP service discovery info. 117 118 @ivar nodeIdentifier: The optional node this info applies to. 119 @type nodeIdentifier: C{unicode} 120 @ivar features: Features as L{DiscoFeature}. 121 @type features: C{set) 122 @ivar identities: Identities as a mapping from (category, type) to name, 123 all C{unicode}. 124 @type identities: C{dict} 125 @ivar extensions: Service discovery extensions as a mapping from the 126 extension form's C{FORM_TYPE} (C{unicode}) to 127 L{data_form.Form}. Forms with no C{FORM_TYPE} field 128 are mapped as C{None}. Note that multiple forms 129 with the same C{FORM_TYPE} have the last in sequence 130 prevail. 131 @type extensions: C{dict} 132 @ivar _items: Sequence of added items. 133 @type _items: C{list} 134 """ 135 136 def __init__(self): 137 self.nodeIdentifier = '' 138 self.features = set() 139 self.identities = {} 140 self.extensions = {} 141 self._items = [] 142 143 144 def __iter__(self): 145 """ 146 Iterator over sequence of items in the order added. 147 """ 148 return iter(self._items) 149 150 151 def append(self, item): 152 """ 153 Add a piece of service discovery info. 154 155 @param item: A feature, identity or extension form. 156 @type item: L{DiscoFeature}, L{DiscoIdentity} or L{data_form.Form} 157 """ 158 self._items.append(item) 159 160 if isinstance(item, DiscoFeature): 161 self.features.add(item) 162 elif isinstance(item, DiscoIdentity): 163 self.identities[(item.category, item.type)] = item.name 164 elif isinstance(item, data_form.Form): 165 self.extensions[item.formNamespace] = item 166 167 168 def toElement(self): 169 """ 170 Generate a DOM representation. 171 172 This takes the items added with C{append} to create a DOM 173 representation of service discovery information. 174 175 @rtype: L{domish.Element}. 176 """ 177 element = domish.Element((NS_DISCO_INFO, 'query')) 178 179 if self.nodeIdentifier: 180 element['node'] = self.nodeIdentifier 181 182 for item in self: 183 element.addChild(item.toElement()) 184 185 return element 186 187 188 @staticmethod 189 def fromElement(element): 190 """ 191 Parse a DOM representation into a L{DiscoInfo} instance. 192 193 @param element: Element that represents the disco info. 194 @type element: L{domish.Element}. 195 @rtype L{DiscoInfo}. 196 """ 197 198 info = DiscoInfo() 199 200 info.nodeIdentifier = element.getAttribute('node', '') 201 202 for child in element.elements(): 203 item = None 204 205 if (child.uri, child.name) == (NS_DISCO_INFO, 'feature'): 206 item = DiscoFeature.fromElement(child) 207 elif (child.uri, child.name) == (NS_DISCO_INFO, 'identity'): 208 item = DiscoIdentity.fromElement(child) 209 elif (child.uri, child.name) == (data_form.NS_X_DATA, 'x'): 210 item = data_form.Form.fromElement(child) 211 212 if item: 213 info.append(item) 214 215 return info 216 217 218 219 class DiscoItem(object): 220 """ 221 XMPP service discovery item. 222 223 @ivar entity: The entity holding the item. 224 @type entity: L{jid.JID} 225 @ivar nodeIdentifier: The optional node identifier for the item. 226 @type nodeIdentifier: C{unicode} 227 @ivar name: The optional natural language name for this entity. 228 @type name: C{unicode} 229 """ 230 231 def __init__(self, entity, nodeIdentifier='', name=None): 232 self.entity = entity 233 self.nodeIdentifier = nodeIdentifier 234 self.name = name 235 236 237 def toElement(self): 238 """ 239 Generate a DOM representation. 240 241 @rtype: L{domish.Element}. 242 """ 243 element = domish.Element((NS_DISCO_ITEMS, 'item')) 244 if self.entity: 245 element['jid'] = self.entity.full() 246 if self.nodeIdentifier: 247 element['node'] = self.nodeIdentifier 248 if self.name: 249 element['name'] = self.name 250 return element 251 252 253 @staticmethod 254 def fromElement(element): 255 """ 256 Parse a DOM representation into a L{DiscoItem} instance. 257 258 @param element: Element that represents the disco iitem. 259 @type element: L{domish.Element}. 260 @rtype L{DiscoItem}. 261 """ 262 try: 263 entity = jid.JID(element.getAttribute('jid', ' ')) 264 except jid.InvalidFormat: 265 entity = None 266 nodeIdentifier = element.getAttribute('node', '') 267 name = element.getAttribute('name') 268 feature = DiscoItem(entity, nodeIdentifier, name) 269 return feature 270 271 272 273 class DiscoItems(object): 274 """ 275 XMPP service discovery items. 276 277 @ivar nodeIdentifier: The optional node this info applies to. 278 @type nodeIdentifier: C{unicode} 279 @ivar _items: Sequence of added items. 280 @type _items: C{list} 281 """ 282 283 def __init__(self): 284 self.nodeIdentifier = '' 285 self._items = [] 286 287 288 def __iter__(self): 289 """ 290 Iterator over sequence of items in the order added. 291 """ 292 return iter(self._items) 293 294 295 def append(self, item): 296 """ 297 Append item to the sequence of items. 298 299 @param item: Item to be added. 300 @type item: L{DiscoItem} 301 """ 302 self._items.append(item) 303 304 305 def toElement(self): 306 """ 307 Generate a DOM representation. 308 309 This takes the items added with C{append} to create a DOM 310 representation of service discovery items. 311 312 @rtype: L{domish.Element}. 313 """ 314 element = domish.Element((NS_DISCO_ITEMS, 'query')) 315 316 if self.nodeIdentifier: 317 element['node'] = self.nodeIdentifier 318 319 for item in self: 320 element.addChild(item.toElement()) 321 322 return element 323 324 325 @staticmethod 326 def fromElement(element): 327 """ 328 Parse a DOM representation into a L{DiscoItems} instance. 329 330 @param element: Element that represents the disco items. 331 @type element: L{domish.Element}. 332 @rtype L{DiscoItems}. 333 """ 334 335 info = DiscoItems() 336 337 info.nodeIdentifier = element.getAttribute('node', '') 338 339 for child in element.elements(): 340 if (child.uri, child.name) == (NS_DISCO_ITEMS, 'item'): 341 item = DiscoItem.fromElement(child) 342 info.append(item) 343 344 return info 345 346 347 348 class _DiscoRequest(xmlstream.IQ): 349 """ 350 Element representing an XMPP service discovery request. 351 """ 352 353 def __init__(self, xs, namespace, nodeIdentifier=''): 354 """ 355 Initialize the request. 356 357 @param xs: XML Stream the request should go out on. 358 @type xs: L{xmlstream.XmlStream} 359 @param namespace: Request namespace. 360 @type namespace: C{str} 361 @param nodeIdentifier: Node to request info from. 362 @type nodeIdentifier: C{unicode} 363 """ 364 xmlstream.IQ.__init__(self, xs, "get") 365 query = self.addElement((namespace, 'query')) 366 if nodeIdentifier: 367 query['node'] = nodeIdentifier 368 369 370 371 class DiscoClientProtocol(XMPPHandler): 372 """ 373 XMPP Service Discovery client protocol. 374 """ 375 376 def requestInfo(self, entity, nodeIdentifier=''): 377 """ 378 Request information discovery from a node. 379 380 @param entity: Entity to send the request to. 381 @type entity: L{jid.JID} 382 @param nodeIdentifier: Optional node to request info from. 383 @type nodeIdentifier: C{unicode} 384 """ 385 386 request = _DiscoRequest(self.xmlstream, NS_DISCO_INFO, nodeIdentifier) 387 388 d = request.send(entity.full()) 389 d.addCallback(lambda iq: DiscoInfo.fromElement(iq.query)) 390 return d 391 392 393 def requestItems(self, entity, nodeIdentifier=''): 394 """ 395 Request items discovery from a node. 396 397 @param entity: Entity to send the request to. 398 @type entity: L{jid.JID} 399 @param nodeIdentifier: Optional node to request info from. 400 @type nodeIdentifier: C{unicode} 401 """ 402 403 request = _DiscoRequest(self.xmlstream, NS_DISCO_ITEMS, nodeIdentifier) 404 405 d = request.send(entity.full()) 406 d.addCallback(lambda iq: DiscoItems.fromElement(iq.query)) 407 return d 408 64 409 65 410 … … 96 441 self.xmlstream.addObserver(DISCO_ITEMS, self.handleRequest) 97 442 98 def _error(self, failure):99 failure.trap(defer.FirstError)100 return failure.value.subFailure101 443 102 444 def _onDiscoInfo(self, iq): 445 """ 446 Called for incoming disco info requests. 447 448 @param iq: The request iq element. 449 @type iq: L{Element<twisted.words.xish.domish.Element>} 450 """ 103 451 requestor = jid.internJID(iq["from"]) 104 452 target = jid.internJID(iq["to"]) 105 453 nodeIdentifier = iq.query.getAttribute("node", '') 106 454 107 def toResponse(results): 108 info = [] 109 for i in results: 110 info.extend(i[1]) 111 455 def toResponse(info): 112 456 if nodeIdentifier and not info: 113 457 raise error.StanzaError('item-not-found') 114 458 else: 115 response = domish.Element((NS_INFO, 'query')) 459 response = DiscoInfo() 460 response.nodeIdentifier = nodeIdentifier 116 461 117 462 for item in info: 118 response.addChild(item) 119 120 return response 121 122 dl = [] 123 for handler in self.parent: 124 if IDisco.providedBy(handler): 125 dl.append(handler.getDiscoInfo(requestor, target, 126 nodeIdentifier)) 127 128 d = defer.DeferredList(dl, fireOnOneErrback=1, consumeErrors=1) 129 d.addCallbacks(toResponse, self._error) 463 response.append(item) 464 465 return response.toElement() 466 467 d = self.info(requestor, target, nodeIdentifier) 468 d.addCallback(toResponse) 130 469 return d 131 470 471 132 472 def _onDiscoItems(self, iq): 473 """ 474 Called for incoming disco items requests. 475 476 @param iq: The request iq element. 477 @type iq: L{Element<twisted.words.xish.domish.Element>} 478 """ 133 479 requestor = jid.internJID(iq["from"]) 134 480 target = jid.internJID(iq["to"]) 135 481 nodeIdentifier = iq.query.getAttribute("node", '') 136 482 137 def toResponse(results): 138 items = [] 139 for i in results: 140 items.extend(i[1]) 141 142 response = domish.Element((NS_ITEMS, 'query')) 483 def toResponse(items): 484 response = DiscoItems() 485 response.nodeIdentifier = nodeIdentifier 143 486 144 487 for item in items: 145 response.addChild(item) 146 147 return response 148 149 dl = [] 150 for handler in self.parent: 151 if IDisco.providedBy(handler): 152 dl.append(handler.getDiscoItems(requestor, target, 153 nodeIdentifier)) 154 155 d = defer.DeferredList(dl, fireOnOneErrback=1, consumeErrors=1) 156 d.addCallbacks(toResponse, self._error) 488 response.append(item) 489 490 return response.toElement() 491 492 d = self.items(requestor, target, nodeIdentifier) 493 d.addCallback(toResponse) 157 494 return d 495 496 497 def _gatherResults(self, deferredList): 498 """ 499 Gather results from a list of deferreds. 500 501 Similar to L{defer.gatherResults}, but flattens the returned results, 502 consumes errors after the first one and fires the errback of the 503 returned deferred with the failure of the first deferred that fires its 504 errback. 505 506 @param deferredList: List of deferreds for which the results should be 507 gathered. 508 @type deferredList: C{list} 509 @return: Deferred that fires with a list of gathered results. 510 @rtype: L{defer.Deferred} 511 """ 512 def cb(resultList): 513 results = [] 514 for success, value in resultList: 515 results.extend(value) 516 return results 517 518 def eb(failure): 519 failure.trap(defer.FirstError) 520 return failure.value.subFailure 521 522 d = defer.DeferredList(deferredList, fireOnOneErrback=1, 523 consumeErrors=1) 524 d.addCallbacks(cb, eb) 525 return d 526 527 528 def info(self, requestor, target, nodeIdentifier): 529 """ 530 Inspect all sibling protocol handlers for disco info. 531 532 Calls the L{getDiscoInfo<IDisco.getDiscoInfo>} method on all child 533 handlers of the parent, that provide L{IDisco}. 534 535 @param requestor: The entity that sent the request. 536 @type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>} 537 @param target: The entity the request was sent to. 538 @type target: L{JID<twisted.words.protocols.jabber.jid.JID>} 539 @param nodeIdentifier: The optional node being queried, or C{''}. 540 @type nodeIdentifier: C{unicode} 541 @return: Deferred with the gathered results from sibling handlers. 542 @rtype: L{defer.Deferred} 543 """ 544 dl = [handler.getDiscoInfo(requestor, target, nodeIdentifier) 545 for handler in self.parent 546 if IDisco.providedBy(handler)] 547 return self._gatherResults(dl) 548 549 550 def items(self, requestor, target, nodeIdentifier): 551 """ 552 Inspect all sibling protocol handlers for disco items. 553 554 Calls the L{getDiscoItems<IDisco.getDiscoItems>} method on all child 555 handlers of the parent, that provide L{IDisco}. 556 557 @param requestor: The entity that sent the request. 558 @type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>} 559 @param target: The entity the request was sent to. 560 @type target: L{JID<twisted.words.protocols.jabber.jid.JID>} 561 @param nodeIdentifier: The optional node being queried, or C{''}. 562 @type nodeIdentifier: C{unicode} 563 @return: Deferred with the gathered results from sibling handlers. 564 @rtype: L{defer.Deferred} 565 """ 566 dl = [handler.getDiscoItems(requestor, target, nodeIdentifier) 567 for handler in self.parent 568 if IDisco.providedBy(handler)] 569 return self._gatherResults(dl)
Note: See TracChangeset
for help on using the changeset viewer.