class Network(JabberClient): def __init__(self, **args): self.isconnected = False # Create a unique jabber resource resource = args.get('resource') or 'python_client' resource += '_' + gethostname() + ':' + str(os.getpid()) + '_' + \ threading.currentThread().getName().lower() self.jid = JID(args['username'], args['host'], resource) osrf.log.log_debug("initializing network with JID %s and host=%s, " "port=%s, username=%s" % (self.jid.as_utf8(), args['host'], \ args['port'], args['username'])) #initialize the superclass JabberClient.__init__(self, self.jid, args['password'], args['host']) self.queue = [] self.receive_callback = None self.transport_error_msg = None def connect(self): JabberClient.connect(self) while not self.isconnected: stream = self.get_stream() act = stream.loop_iter(10) if not act: self.idle() def set_receive_callback(self, func): """The callback provided is called when a message is received. The only argument to the function is the received message. """ self.receive_callback = func def session_started(self): osrf.log.log_info("Successfully connected to the opensrf network") self.authenticated() self.stream.set_message_handler("normal", self.message_received) self.stream.set_message_handler("error", self.error_received) self.isconnected = True def send(self, message): """Sends the provided network message.""" osrf.log.log_internal("jabber sending to %s: %s" % (message.recipient, message.body)) message.sender = self.jid.as_utf8() msg = message.make_xmpp_msg() self.stream.send(msg) def error_received(self, stanza): self.transport_error_msg = NetworkMessage(stanza) osrf.log.log_error("XMPP error message received from %s" % self.transport_error_msg.sender) def message_received(self, stanza): """Handler for received messages.""" if stanza.get_type() == "headline": return True # check for errors osrf.log.log_internal("jabber received message from %s : %s" % (stanza.get_from().as_utf8(), stanza.get_body())) self.queue.append(NetworkMessage(stanza)) return True def stream_closed(self, stream): osrf.log.log_debug("XMPP Stream closing...") def stream_error(self, err): osrf.log.log_error("XMPP Stream error: condition: %s %r" % (err.get_condition().name, err.serialize())) def disconnected(self): osrf.log.log_internal('XMPP Disconnected') def recv(self, timeout=120): """Attempts to receive a message from the network. timeout - max number of seconds to wait for a message. If a message is received in 'timeout' seconds, the message is passed to the receive_callback is called and True is returned. Otherwise, false is returned. """ forever = False if timeout < 0: forever = True timeout = None if len(self.queue) == 0: while (forever or timeout >= 0) and len(self.queue) == 0: starttime = time.time() stream = self.get_stream() if not stream: raise XMPPNoConnection('We lost our server connection...') act = stream.loop_iter(timeout) endtime = time.time() - starttime if not forever: timeout -= endtime osrf.log.log_internal("exiting stream loop after %s seconds. " "act=%s, queue size=%d" % (str(endtime), act, len(self.queue))) if self.transport_error_msg: msg = self.transport_error_msg self.transport_error_msg = None raise XMPPNoRecipient(msg.sender) if not act: self.idle() # if we've acquired a message, handle it msg = None if len(self.queue) > 0: msg = self.queue.pop(0) if self.receive_callback: self.receive_callback(msg) return msg def flush_inbound_data(self): ''' Read all pending inbound messages from the socket and discard them ''' cb = self.receive_callback self.receive_callback = None while self.recv(0): pass self.receive_callback = cb
class Delay(StanzaPayloadObject): """ Delayed delivery tag. Represents 'urn:xmpp:delay' (XEP-0203) element of a Jabber stanza. :Ivariables: - `delay_from`: the "from" value of the delay element - `reason`: the "reason" (content) of the delay element - `timestamp`: the UTC timestamp as naive datetime object """ xml_element_name = "delay" xml_element_namespace = DELAY_NS _sort_order = 1 _time_format = "%Y-%m-%dT%H:%M:%SZ" def __init__(self, node_or_datetime, delay_from=None, reason=None, utc=True): """ Initialize the Delay object. :Parameters: - `node_or_datetime`: an XML node to parse or the timestamp. - `delay_from`: JID of the entity which adds the delay mark (when `node_or_datetime` is a timestamp). - `reason`: reason of the delay (when `node_or_datetime` is a timestamp). - `utc`: if `True` then the timestamp is assumed to be UTC, otherwise it is assumed to be local time. :Types: - `node_or_datetime`: `libxml2.xmlNode` or `datetime.datetime` - `delay_from`: `pyxmpp.JID` - `reason`: `unicode` - `utc`: `bool`""" if isinstance(node_or_datetime, libxml2.xmlNode): self.from_xml(node_or_datetime) else: if utc: self.timestamp = node_or_datetime else: self.timestamp = datetime_local_to_utc(node_or_datetime) self.delay_from = JID(delay_from) self.reason = unicode(reason) def from_xml(self, xmlnode): """Initialize Delay object from an XML node. :Parameters: - `xmlnode`: the jabber:x:delay XML element. :Types: - `xmlnode`: `libxml2.xmlNode`""" if xmlnode.type != "element": raise ValueError, "XML node is not a jabber:x:delay element (not an element)" ns = get_node_ns_uri(xmlnode) if ns and (ns != self.xml_element_namespace or xmlnode.name != self.xml_element_name): raise ValueError, "XML node is not a " + self.xml_element_namespace + " element" stamp = xmlnode.prop("stamp") tm = _parse_ts(stamp) tm = tm[0:8] + (0, ) self.timestamp = datetime.datetime.fromtimestamp(time.mktime(tm)) delay_from = from_utf8(xmlnode.prop("from")) if delay_from: try: self.delay_from = JID(delay_from) except JIDError: raise JIDMalformedProtocolError, "Bad JID in the " + self.xml_element_namespace + " 'from' attribute" else: self.delay_from = None self.reason = from_utf8(xmlnode.getContent()) def complete_xml_element(self, xmlnode, _unused): """Complete the XML node with `self` content. Should be overriden in classes derived from `StanzaPayloadObject`. :Parameters: - `xmlnode`: XML node with the element being built. It has already right name and namespace, but no attributes or content. - `_unused`: document to which the element belongs. :Types: - `xmlnode`: `libxml2.xmlNode` - `_unused`: `libxml2.xmlDoc`""" tm = self.timestamp.strftime(self._time_format) xmlnode.setProp("stamp", tm) if self.delay_from: xmlnode.setProp("from", self.delay_from.as_utf8()) if self.reason: xmlnode.setContent(to_utf8(self.reason)) def get_datetime_local(self): """Get the timestamp as a local time. :return: the timestamp of the delay element represented in the local timezone. :returntype: `datetime.datetime`""" r = datetime_utc_to_local(self.timestamp) return r def get_datetime_utc(self): """Get the timestamp as a UTC. :return: the timestamp of the delay element represented in UTC. :returntype: `datetime.datetime`""" return self.timestamp def __str__(self): n = self.as_xml() r = n.serialize() n.freeNode() return r def __cmp__(self, other): return cmp((self._sort_order, self.timestamp), (other._sort_order, other.timestamp))
def __init__(self, name_or_xmlnode, from_jid=None, to_jid=None, stanza_type=None, stanza_id=None, error=None, error_cond=None, stream = None): """Initialize a Stanza object. :Parameters: - `name_or_xmlnode`: XML node to be wrapped into the Stanza object or other Presence object to be copied. If not given then new presence stanza is created using following parameters. - `from_jid`: sender JID. - `to_jid`: recipient JID. - `stanza_type`: staza type: one of: "get", "set", "result" or "error". - `stanza_id`: stanza id -- value of stanza's "id" attribute. If not given, then unique for the session value is generated. - `error`: error object. Ignored if `stanza_type` is not "error". - `error_cond`: error condition name. Ignored if `stanza_type` is not "error" or `error` is not None. :Types: - `name_or_xmlnode`: `unicode` or `libxml2.xmlNode` or `Stanza` - `from_jid`: `JID` - `to_jid`: `JID` - `stanza_type`: `unicode` - `stanza_id`: `unicode` - `error`: `pyxmpp.error.StanzaErrorNode` - `error_cond`: `unicode`""" self._error=None self.xmlnode=None if isinstance(name_or_xmlnode,Stanza): self.xmlnode=name_or_xmlnode.xmlnode.copyNode(1) common_doc.addChild(self.xmlnode) elif isinstance(name_or_xmlnode,libxml2.xmlNode): self.xmlnode=name_or_xmlnode.docCopyNode(common_doc,1) common_doc.addChild(self.xmlnode) try: ns = self.xmlnode.ns() except libxml2.treeError: ns = None if not ns or not ns.name: xmlextra.replace_ns(self.xmlnode, ns, common_ns) else: self.xmlnode=common_doc.newChild(common_ns,name_or_xmlnode,None) if from_jid is not None: if not isinstance(from_jid,JID): from_jid=JID(from_jid) self.xmlnode.setProp("from",from_jid.as_utf8()) if to_jid is not None: if not isinstance(to_jid,JID): to_jid=JID(to_jid) self.xmlnode.setProp("to",to_jid.as_utf8()) if stanza_type: self.xmlnode.setProp("type",stanza_type) if stanza_id: self.xmlnode.setProp("id",stanza_id) if self.get_type()=="error": from pyxmpp.error import StanzaErrorNode if error: self._error=StanzaErrorNode(error,parent=self.xmlnode,copy=1) elif error_cond: self._error=StanzaErrorNode(error_cond,parent=self.xmlnode) self.stream = stream
class Network(JabberClient): def __init__(self, **args): self.isconnected = False # Create a unique jabber resource resource = args.get('resource') or 'python_client' resource += '_' + gethostname() + ':' + str(os.getpid()) + '_' + \ threading.currentThread().getName().lower() self.jid = JID(args['username'], args['host'], resource) osrf.log.log_debug("initializing network with JID %s and host=%s, " "port=%s, username=%s" % (self.jid.as_utf8(), args['host'], \ args['port'], args['username'])) #initialize the superclass JabberClient.__init__(self, self.jid, args['password'], args['host']) self.queue = [] self.receive_callback = None self.transport_error_msg = None def connect(self): JabberClient.connect(self) while not self.isconnected: stream = self.get_stream() act = stream.loop_iter(10) if not act: self.idle() def set_receive_callback(self, func): """The callback provided is called when a message is received. The only argument to the function is the received message. """ self.receive_callback = func def session_started(self): osrf.log.log_info("Successfully connected to the opensrf network") self.authenticated() self.stream.set_message_handler("normal", self.message_received) self.stream.set_message_handler("error", self.error_received) self.isconnected = True def send(self, message): """Sends the provided network message.""" osrf.log.log_internal("jabber sending to %s: %s" % (message.recipient, message.body)) message.sender = self.jid.as_utf8() msg = message.make_xmpp_msg() self.stream.send(msg) def error_received(self, stanza): self.transport_error_msg = NetworkMessage(stanza) osrf.log.log_error("XMPP error message received from %s" % self.transport_error_msg.sender) def message_received(self, stanza): """Handler for received messages.""" if stanza.get_type()=="headline": return True # check for errors osrf.log.log_internal("jabber received message from %s : %s" % (stanza.get_from().as_utf8(), stanza.get_body())) self.queue.append(NetworkMessage(stanza)) return True def stream_closed(self, stream): osrf.log.log_debug("XMPP Stream closing...") def stream_error(self, err): osrf.log.log_error("XMPP Stream error: condition: %s %r" % (err.get_condition().name,err.serialize())) def disconnected(self): osrf.log.log_internal('XMPP Disconnected') def recv(self, timeout=120): """Attempts to receive a message from the network. timeout - max number of seconds to wait for a message. If a message is received in 'timeout' seconds, the message is passed to the receive_callback is called and True is returned. Otherwise, false is returned. """ forever = False if timeout < 0: forever = True timeout = None if len(self.queue) == 0: while (forever or timeout >= 0) and len(self.queue) == 0: starttime = time.time() stream = self.get_stream() if not stream: raise XMPPNoConnection('We lost our server connection...') act = stream.loop_iter(timeout) endtime = time.time() - starttime if not forever: timeout -= endtime osrf.log.log_internal("exiting stream loop after %s seconds. " "act=%s, queue size=%d" % (str(endtime), act, len(self.queue))) if self.transport_error_msg: msg = self.transport_error_msg self.transport_error_msg = None raise XMPPNoRecipient(msg.sender) if not act: self.idle() # if we've acquired a message, handle it msg = None if len(self.queue) > 0: msg = self.queue.pop(0) if self.receive_callback: self.receive_callback(msg) return msg def flush_inbound_data(self): ''' Read all pending inbound messages from the socket and discard them ''' cb = self.receive_callback self.receive_callback = None while self.recv(0): pass self.receive_callback = cb
class Delay(StanzaPayloadObject): """ Delayed delivery tag. Represents 'urn:xmpp:delay' (XEP-0203) element of a Jabber stanza. :Ivariables: - `delay_from`: the "from" value of the delay element - `reason`: the "reason" (content) of the delay element - `timestamp`: the UTC timestamp as naive datetime object """ xml_element_name = "delay" xml_element_namespace = DELAY_NS _sort_order = 1 _time_format = "%Y-%m-%dT%H:%M:%SZ" def __init__(self,node_or_datetime,delay_from=None,reason=None,utc=True): """ Initialize the Delay object. :Parameters: - `node_or_datetime`: an XML node to parse or the timestamp. - `delay_from`: JID of the entity which adds the delay mark (when `node_or_datetime` is a timestamp). - `reason`: reason of the delay (when `node_or_datetime` is a timestamp). - `utc`: if `True` then the timestamp is assumed to be UTC, otherwise it is assumed to be local time. :Types: - `node_or_datetime`: `libxml2.xmlNode` or `datetime.datetime` - `delay_from`: `pyxmpp.JID` - `reason`: `unicode` - `utc`: `bool`""" if isinstance(node_or_datetime,libxml2.xmlNode): self.from_xml(node_or_datetime) else: if utc: self.timestamp=node_or_datetime else: self.timestamp=datetime_local_to_utc(node_or_datetime) self.delay_from=JID(delay_from) self.reason=unicode(reason) def from_xml(self,xmlnode): """Initialize Delay object from an XML node. :Parameters: - `xmlnode`: the jabber:x:delay XML element. :Types: - `xmlnode`: `libxml2.xmlNode`""" if xmlnode.type!="element": raise ValueError,"XML node is not a jabber:x:delay element (not an element)" ns=get_node_ns_uri(xmlnode) if ns and (ns != self.xml_element_namespace or xmlnode.name != self.xml_element_name): raise ValueError,"XML node is not a " + self.xml_element_namespace + " element" stamp=xmlnode.prop("stamp") tm = _parse_ts(stamp) tm=tm[0:8]+(0,) self.timestamp=datetime.datetime.fromtimestamp(time.mktime(tm)) delay_from=from_utf8(xmlnode.prop("from")) if delay_from: try: self.delay_from = JID(delay_from) except JIDError: raise JIDMalformedProtocolError, "Bad JID in the " + self.xml_element_namespace + " 'from' attribute" else: self.delay_from = None self.reason = from_utf8(xmlnode.getContent()) def complete_xml_element(self, xmlnode, _unused): """Complete the XML node with `self` content. Should be overriden in classes derived from `StanzaPayloadObject`. :Parameters: - `xmlnode`: XML node with the element being built. It has already right name and namespace, but no attributes or content. - `_unused`: document to which the element belongs. :Types: - `xmlnode`: `libxml2.xmlNode` - `_unused`: `libxml2.xmlDoc`""" tm=self.timestamp.strftime(self._time_format) xmlnode.setProp("stamp",tm) if self.delay_from: xmlnode.setProp("from",self.delay_from.as_utf8()) if self.reason: xmlnode.setContent(to_utf8(self.reason)) def get_datetime_local(self): """Get the timestamp as a local time. :return: the timestamp of the delay element represented in the local timezone. :returntype: `datetime.datetime`""" r=datetime_utc_to_local(self.timestamp) return r def get_datetime_utc(self): """Get the timestamp as a UTC. :return: the timestamp of the delay element represented in UTC. :returntype: `datetime.datetime`""" return self.timestamp def __str__(self): n=self.as_xml() r=n.serialize() n.freeNode() return r def __cmp__(self, other): return cmp((self._sort_order, self.timestamp), (other._sort_order, other.timestamp))