More doc-string (and not only) refactoring

This commit is contained in:
Alexander Cherniuk 2009-11-26 16:32:56 +02:00
parent 99472b1702
commit 6d0f28c47d
9 changed files with 1350 additions and 744 deletions

View File

@ -13,12 +13,14 @@
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
'''
"""
Provides plugs for SASL and NON-SASL authentication mechanisms.
Can be used both for client and transport authentication.
Can be used both for client and transport authentication
See client_nb.py
'''
"""
from protocol import NS_SASL, NS_SESSION, NS_STREAMS, NS_BIND, NS_AUTH
from protocol import Node, NodeProcessed, isResultNode, Iq, Protocol, JID
from plugin import PlugIn
@ -49,7 +51,8 @@ SASL_UNSUPPORTED = 'not-supported'
SASL_IN_PROCESS = 'in-process'
def challenge_splitter(data):
''' Helper function that creates a dict from challenge string.
"""
Helper function that creates a dict from challenge string
Sample challenge string:
username="example.org",realm="somerealm",\
@ -59,7 +62,7 @@ def challenge_splitter(data):
Expected result for challan:
dict['qop'] = ('auth','auth-int','auth-conf')
dict['realm'] = 'somerealm'
'''
"""
X_KEYWORD, X_VALUE, X_END = 0, 1, 2
quotes_open = False
keyword, value = '', ''
@ -112,16 +115,17 @@ def challenge_splitter(data):
class SASL(PlugIn):
'''
"""
Implements SASL authentication. Can be plugged into NonBlockingClient
to start authentication.
'''
to start authentication
"""
def __init__(self, username, password, on_sasl):
'''
"""
:param user: XMPP username
:param password: XMPP password
:param on_sasl: Callback, will be called after each SASL auth-step.
'''
"""
PlugIn.__init__(self)
self.username = username
self.password = password
@ -141,7 +145,9 @@ class SASL(PlugIn):
self.startsasl = None
def plugout(self):
''' Remove SASL handlers from owner's dispatcher. Used internally. '''
"""
Remove SASL handlers from owner's dispatcher. Used internally
"""
if 'features' in self._owner.__dict__:
self._owner.UnregisterHandler('features', self.FeaturesHandler,
xmlns=NS_STREAMS)
@ -156,13 +162,13 @@ class SASL(PlugIn):
xmlns=NS_SASL)
def auth(self):
'''
"""
Start authentication. Result can be obtained via "SASL.startsasl"
attribute and will be either SASL_SUCCESS or SASL_FAILURE.
attribute and will be either SASL_SUCCESS or SASL_FAILURE
Note that successfull auth will take at least two Dispatcher.Process()
calls.
'''
"""
if self.startsasl:
pass
elif self._owner.Dispatcher.Stream.features:
@ -176,7 +182,9 @@ class SASL(PlugIn):
self.FeaturesHandler, xmlns=NS_STREAMS)
def FeaturesHandler(self, conn, feats):
''' Used to determine if server supports SASL auth. Used internally. '''
"""
Used to determine if server supports SASL auth. Used internally
"""
if not feats.getTag('mechanisms', namespace=NS_SASL):
self.startsasl='not-supported'
log.error('SASL not supported by server')
@ -235,7 +243,9 @@ class SASL(PlugIn):
return
def SASLHandler(self, conn, challenge):
''' Perform next SASL auth step. Used internally. '''
"""
Perform next SASL auth step. Used internally
"""
if challenge.getNamespace() != NS_SASL:
return
### Handle Auth result
@ -359,12 +369,15 @@ class SASL(PlugIn):
class NonBlockingNonSASL(PlugIn):
'''
"""
Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and
transport authentication.
'''
transport authentication
"""
def __init__(self, user, password, resource, on_auth):
''' Caches username, password and resource for auth. '''
"""
Caches username, password and resource for auth
"""
PlugIn.__init__(self)
self.user = user
if password is None:
@ -375,10 +388,10 @@ class NonBlockingNonSASL(PlugIn):
self.on_auth = on_auth
def plugin(self, owner):
'''
"""
Determine the best auth method (digest/0k/plain) and use it for auth.
Returns used method name on success. Used internally.
'''
Returns used method name on success. Used internally
"""
log.info('Querying server about possible auth methods')
self.owner = owner
@ -438,10 +451,11 @@ class NonBlockingNonSASL(PlugIn):
class NonBlockingBind(PlugIn):
'''
"""
Bind some JID to the current connection to allow router know of our
location. Must be plugged after successful SASL auth.
'''
location. Must be plugged after successful SASL auth
"""
def __init__(self):
PlugIn.__init__(self)
self.bound = None
@ -459,10 +473,10 @@ class NonBlockingBind(PlugIn):
xmlns=NS_STREAMS)
def FeaturesHandler(self, conn, feats):
'''
"""
Determine if server supports resource binding and set some internal
attributes accordingly.
'''
attributes accordingly
"""
if not feats.getTag('bind', namespace=NS_BIND):
log.error('Server does not requested binding.')
# we try to bind resource anyway
@ -476,14 +490,16 @@ class NonBlockingBind(PlugIn):
self.bound = []
def plugout(self):
''' Remove Bind handler from owner's dispatcher. Used internally. '''
"""
Remove Bind handler from owner's dispatcher. Used internally
"""
self._owner.UnregisterHandler('features', self.FeaturesHandler,
xmlns=NS_STREAMS)
def NonBlockingBind(self, resource=None, on_bound=None):
''' Perform binding.
Use provided resource name or random (if not provided).
'''
"""
Perform binding. Use provided resource name or random (if not provided).
"""
self.on_bound = on_bound
self._resource = resource
if self._resource:

View File

@ -125,11 +125,12 @@ class NonBlockingBOSH(NonBlockingTransport):
log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd))
def on_http_request_possible(self):
'''
"""
Called when HTTP request it's possible to send a HTTP request. It can be when
socket is connected or when HTTP response arrived.
socket is connected or when HTTP response arrived
There should be always one pending request to BOSH CM.
'''
"""
log.debug('on_http_req possible, state:\n%s' % self.get_current_state())
if self.get_state()==DISCONNECTED: return
@ -149,14 +150,18 @@ class NonBlockingBOSH(NonBlockingTransport):
def get_socket_in(self, state):
''' gets sockets in desired state '''
"""
Get sockets in desired state
"""
for s in self.http_socks:
if s.get_state()==state: return s
return None
def get_free_socket(self):
''' Selects and returns socket eligible for sending a data to.'''
"""
Select and returns socket eligible for sending a data to
"""
if self.http_pipelining:
return self.get_socket_in(CONNECTED)
else:
@ -176,10 +181,10 @@ class NonBlockingBOSH(NonBlockingTransport):
def send_BOSH(self, payload):
'''
"""
Tries to send a stanza in payload by appeding it to a buffer and plugging a
free socket for writing.
'''
"""
total_pending_reqs = sum([s.pending_requests for s in self.http_socks])
# when called after HTTP response (Payload=None) and when there are already
@ -236,15 +241,16 @@ class NonBlockingBOSH(NonBlockingTransport):
log.error('=====!!!!!!!!====> Couldn\'t get free socket in plug_socket())')
def build_stanza(self, socket):
'''
Builds a BOSH body tag from data in buffers and adds key, rid and ack
attributes to it.
"""
Build a BOSH body tag from data in buffers and adds key, rid and ack
attributes to it
This method is called from _do_send() of underlying transport. This is to
ensure rid and keys will be processed in correct order. If I generate them
before plugging a socket for write (and did it for two sockets/HTTP
connections) in parallel, they might be sent in wrong order, which results
in violating the BOSH session and server-side disconnect.
'''
ensure rid and keys will be processed in correct order. If I generate
them before plugging a socket for write (and did it for two sockets/HTTP
connections) in parallel, they might be sent in wrong order, which
results in violating the BOSH session and server-side disconnect.
"""
if self.prio_bosh_stanzas:
stanza, add_payload = self.prio_bosh_stanzas.pop(0)
if add_payload:
@ -285,10 +291,11 @@ class NonBlockingBOSH(NonBlockingTransport):
self.wait_cb_time)
def on_persistent_fallback(self, socket):
'''
Called from underlying transport when server closes TCP connection.
"""
Called from underlying transport when server closes TCP connection
:param socket: disconnected transport object
'''
"""
if socket.http_persistent:
log.warn('Fallback to nonpersistent HTTP (no pipelining as well)')
socket.http_persistent = False
@ -302,9 +309,10 @@ class NonBlockingBOSH(NonBlockingTransport):
def handle_body_attrs(self, stanza_attrs):
'''
Called for each incoming body stanza from dispatcher. Checks body attributes.
'''
"""
Called for each incoming body stanza from dispatcher. Checks body
attributes.
"""
self.remove_bosh_wait_timeout()
if self.after_init:
@ -345,7 +353,9 @@ class NonBlockingBOSH(NonBlockingTransport):
def append_stanza(self, stanza):
''' appends stanza to a buffer to send '''
"""
Append stanza to a buffer to send
"""
if stanza:
if isinstance(stanza, tuple):
# stanza is tuple of BOSH stanza and bool value for whether to add payload
@ -378,7 +388,9 @@ class NonBlockingBOSH(NonBlockingTransport):
def boshify_stanzas(self, stanzas=[], body_attrs=None):
''' wraps zero to many stanzas by body tag with xmlns and sid '''
"""
Wraps zero to many stanzas by body tag with xmlns and sid
"""
log.debug('boshify_staza - type is: %s, stanza is %s' % (type(stanzas), stanzas))
tag = BOSHBody(attrs={'sid': self.bosh_sid})
tag.setPayload(stanzas)
@ -470,10 +482,10 @@ def get_rand_number():
class AckChecker():
'''
"""
Class for generating rids and generating and checking acknowledgements in
BOSH messages.
'''
BOSH messages
"""
def __init__(self):
self.rid = get_rand_number()
self.ack = 1
@ -516,9 +528,9 @@ class AckChecker():
class KeyStack():
'''
"""
Class implementing key sequences for BOSH messages
'''
"""
def __init__(self, count):
self.count = count
self.keys = []

View File

@ -18,7 +18,10 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
''' XML canonicalisation methods (for XEP-0116) '''
"""
XML canonicalisation methods (for XEP-0116)
"""
from simplexml import ustr
def c14n(node, is_buggy):

View File

@ -16,9 +16,10 @@
# $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $
'''
"""
Client class establishs connection to XMPP Server and handles authentication
'''
"""
import socket
import transports_nb, dispatcher_nb, auth_nb, roster_nb, protocol, bosh
from protocol import NS_TLS
@ -28,21 +29,22 @@ log = logging.getLogger('gajim.c.x.client_nb')
class NonBlockingClient:
'''
"""
Client class is XMPP connection mountpoint. Objects for authentication,
network communication, roster, xml parsing ... are plugged to client object.
Client implements the abstract behavior - mostly negotioation and callbacks
handling, whereas underlying modules take care of feature-specific logic.
'''
handling, whereas underlying modules take care of feature-specific logic
"""
def __init__(self, domain, idlequeue, caller=None):
'''
Caches connection data:
"""
Caches connection data
:param domain: domain - for to: attribute (from account info)
:param idlequeue: processing idlequeue
:param caller: calling object - it has to implement methods
_event_dispatcher which is called from dispatcher instance
'''
"""
self.Namespace = protocol.NS_CLIENT
self.defaultNamespace = self.Namespace
@ -69,10 +71,10 @@ class NonBlockingClient:
self.protocol_type = 'XMPP'
def disconnect(self, message=''):
'''
Called on disconnection - disconnect callback is picked based on state
of the client.
'''
"""
Called on disconnection - disconnect callback is picked based on state of
the client.
"""
# to avoid recursive calls
if self.disconnecting: return
@ -132,9 +134,10 @@ class NonBlockingClient:
self.disconnecting = False
def connect(self, on_connect, on_connect_failure, hostname=None, port=5222,
on_proxy_failure=None, proxy=None, secure_tuple=('plain', None, None)):
'''
Open XMPP connection (open XML streams in both directions).
on_proxy_failure=None, proxy=None, secure_tuple=('plain', None,
None)):
"""
Open XMPP connection (open XML streams in both directions)
:param on_connect: called after stream is successfully opened
:param on_connect_failure: called when error occures during connection
@ -150,7 +153,7 @@ class NonBlockingClient:
'tls' - TLS established after negotiation with starttls, or 'plain'.
cacerts, mycerts - see tls_nb.NonBlockingTLS constructor for more
details
'''
"""
self.on_connect = on_connect
self.on_connect_failure=on_connect_failure
self.on_proxy_failure = on_proxy_failure
@ -223,7 +226,11 @@ class NonBlockingClient:
on_success=self._try_next_ip)
def _resolve_hostname(self, hostname, port, on_success):
''' wrapper for getaddinfo call. FIXME: getaddinfo blocks'''
"""
Wrapper for getaddinfo call
FIXME: getaddinfo blocks
"""
try:
self.ip_addresses = socket.getaddrinfo(hostname, port,
socket.AF_UNSPEC, socket.SOCK_STREAM)
@ -234,7 +241,9 @@ class NonBlockingClient:
on_success()
def _try_next_ip(self, err_message=None):
'''Iterates over IP addresses tries to connect to it'''
"""
Iterate over IP addresses tries to connect to it
"""
if err_message:
log.debug('While looping over DNS A records: %s' % err_message)
if self.ip_addresses == []:
@ -249,18 +258,20 @@ class NonBlockingClient:
on_connect_failure=self._try_next_ip)
def incoming_stream_version(self):
''' gets version of xml stream'''
"""
Get version of xml stream
"""
if 'version' in self.Dispatcher.Stream._document_attrs:
return self.Dispatcher.Stream._document_attrs['version']
else:
return None
def _xmpp_connect(self, socket_type=None):
'''
Starts XMPP connecting process - opens the XML stream. Is called after TCP
"""
Start XMPP connecting process - open the XML stream. Is called after TCP
connection is established or after switch to TLS when successfully
negotiated with <starttls>.
'''
"""
# socket_type contains info which transport connection was established
if not socket_type:
if self.Connection.ssl_lib:
@ -273,19 +284,19 @@ class NonBlockingClient:
self._xmpp_connect_machine()
def _xmpp_connect_machine(self, mode=None, data=None):
'''
Finite automaton taking care of stream opening and features tag
handling. Calls _on_stream_start when stream is started, and disconnect()
on failure.
'''
"""
Finite automaton taking care of stream opening and features tag handling.
Calls _on_stream_start when stream is started, and disconnect() on
failure.
"""
log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s...' %
(mode, str(data)[:20]))
def on_next_receive(mode):
'''
Sets desired on_receive callback on transport based on the state of
"""
Set desired on_receive callback on transport based on the state of
connect_machine.
'''
"""
log.info('setting %s on next receive' % mode)
if mode is None:
self.onreceive(None) # switch to Dispatcher.ProcessNonBlocking
@ -346,10 +357,10 @@ class NonBlockingClient:
self._on_stream_start()
def _on_stream_start(self):
'''
"""
Called after XMPP stream is opened. TLS negotiation may follow if
supported and desired.
'''
"""
self.stream_started = True
self.onreceive(None)
@ -381,7 +392,9 @@ class NonBlockingClient:
assert False, 'Stream opened for unsupported connection'
def _tls_negotiation_handler(self, con=None, tag=None):
''' takes care of TLS negotioation with <starttls> '''
"""
Take care of TLS negotioation with <starttls>
"""
log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag)
if not con and not tag:
# starting state when we send the <starttls>
@ -407,15 +420,17 @@ class NonBlockingClient:
on_fail = lambda: self.disconnect('error while etabilishing TLS'))
def _on_connect(self):
''' preceeds call of on_connect callback '''
"""
Preceed call of on_connect callback
"""
self.onreceive(None)
self.on_connect(self, self.connected)
def raise_event(self, event_type, data):
'''
Raises event to connection instance. DATA_SENT and DATA_RECIVED events
"""
Raise event to connection instance. DATA_SENT and DATA_RECIVED events
are used in XML console to show XMPP traffic
'''
"""
log.info('raising event from transport: :::::%s::::\n_____________\n%s\n_____________\n' % (event_type,data))
if hasattr(self, 'Dispatcher'):
self.Dispatcher.Event('', event_type, data)
@ -425,9 +440,9 @@ class NonBlockingClient:
###############################################################################
def auth(self, user, password, resource='', sasl=True, on_auth=None):
'''
"""
Authenticate connnection and bind resource. If resource is not provided
random one or library name used.
random one or library name used
:param user: XMPP username
:param password: XMPP password
@ -435,7 +450,7 @@ class NonBlockingClient:
:param sasl: Boolean indicating if SASL shall be used. (default: True)
:param on_auth: Callback, called after auth. On auth failure, argument
is None.
'''
"""
self._User, self._Password = user, password
self._Resource, self._sasl = resource, sasl
self.on_auth = on_auth
@ -443,7 +458,9 @@ class NonBlockingClient:
return
def _on_old_auth(self, res):
''' Callback used by NON-SASL auth. On auth failure, res is None. '''
"""
Callback used by NON-SASL auth. On auth failure, res is None
"""
if res:
self.connected += '+old_auth'
self.on_auth(self, 'old_auth')
@ -451,7 +468,9 @@ class NonBlockingClient:
self.on_auth(self, None)
def _on_sasl_auth(self, res):
''' Used internally. On auth failure, res is None. '''
"""
Used internally. On auth failure, res is None
"""
self.onreceive(None)
if res:
self.connected += '+sasl'
@ -460,7 +479,9 @@ class NonBlockingClient:
self.on_auth(self, None)
def _on_doc_attrs(self):
''' Plug authentication objects and start auth. '''
"""
Plug authentication objects and start auth
"""
if self._sasl:
auth_nb.SASL.get_instance(self._User, self._Password,
self._on_start_sasl).PlugIn(self)
@ -474,7 +495,9 @@ class NonBlockingClient:
return True
def _on_start_sasl(self, data=None):
''' Callback used by SASL, called on each auth step.'''
"""
Callback used by SASL, called on each auth step
"""
if data:
self.Dispatcher.ProcessNonBlocking(data)
if not 'SASL' in self.__dict__:
@ -504,20 +527,26 @@ class NonBlockingClient:
return True
def initRoster(self, version=''):
''' Plug in the roster. '''
"""
Plug in the roster
"""
if not self.__dict__.has_key('NonBlockingRoster'):
return roster_nb.NonBlockingRoster.get_instance(version=version).PlugIn(self)
def getRoster(self, on_ready=None, force=False):
''' Return the Roster instance, previously plugging it in and
requesting roster from server if needed. '''
"""
Return the Roster instance, previously plugging it in and requesting
roster from server if needed
"""
if self.__dict__.has_key('NonBlockingRoster'):
return self.NonBlockingRoster.getRoster(on_ready, force)
return None
def sendPresence(self, jid=None, typ=None, requestRoster=0):
''' Send some specific presence state.
Can also request roster from server if according agrument is set.'''
"""
Send some specific presence state. Can also request roster from server if
according agrument is set
"""
if requestRoster:
# FIXME: used somewhere?
roster_nb.NonBlockingRoster.get_instance().PlugIn(self)
@ -528,31 +557,38 @@ class NonBlockingClient:
###############################################################################
def RegisterDisconnectHandler(self,handler):
''' Register handler that will be called on disconnect.'''
"""
Register handler that will be called on disconnect
"""
self.disconnect_handlers.append(handler)
def UnregisterDisconnectHandler(self,handler):
''' Unregister handler that is called on disconnect.'''
"""
Unregister handler that is called on disconnect
"""
self.disconnect_handlers.remove(handler)
def DisconnectHandler(self):
'''
"""
Default disconnect handler. Just raises an IOError. If you choosed to use
this class in your production client, override this method or at least
unregister it.
'''
"""
raise IOError('Disconnected from server.')
def get_connect_type(self):
''' Returns connection state. F.e.: None / 'tls' / 'plain+non_sasl'. '''
"""
Return connection state. F.e.: None / 'tls' / 'plain+non_sasl'
"""
return self.connected
def get_peerhost(self):
'''
"""
Gets the ip address of the account, from which is made connection to the
server (e.g. IP and port of gajim's socket).
server (e.g. IP and port of gajim's socket)
We will create listening socket on the same ip
'''
"""
# FIXME: tuple (ip, port) is expected (and checked for) but port num is
# useless
return self.socket.peerhost

View File

@ -15,10 +15,10 @@
## GNU General Public License for more details.
'''
"""
Main xmpp decision making logic. Provides library with methods to assign
different handlers to different XMPP stanzas and namespaces.
'''
different handlers to different XMPP stanzas and namespaces
"""
import simplexml, sys, locale
from xml.parsers.expat import ExpatError
@ -36,7 +36,7 @@ XML_DECLARATION = '<?xml version=\'1.0\'?>'
# FIXME: ugly
class Dispatcher():
'''
"""
Why is this here - I needed to redefine Dispatcher for BOSH and easiest way
was to inherit original Dispatcher (now renamed to XMPPDispatcher). Trouble
is that reference used to access dispatcher instance is in Client attribute
@ -46,7 +46,8 @@ class Dispatcher():
If having two kinds of dispatcher will go well, I will rewrite the dispatcher
references in other scripts
'''
"""
def PlugIn(self, client_obj, after_SASL=False, old_features=None):
if client_obj.protocol_type == 'XMPP':
XMPPDispatcher().PlugIn(client_obj)
@ -57,22 +58,23 @@ class Dispatcher():
@classmethod
def get_instance(cls, *args, **kwargs):
'''
Factory Method for object creation.
"""
Factory Method for object creation
Use this instead of directly initializing the class in order to make
unit testing much easier.
'''
"""
return cls(*args, **kwargs)
class XMPPDispatcher(PlugIn):
'''
Handles XMPP stream and is the first who takes control over a fresh stanza.
"""
Handles XMPP stream and is the first who takes control over a fresh stanza
Is plugged into NonBlockingClient but can be replugged to restart handled
stream headers (used by SASL f.e.).
'''
"""
def __init__(self):
PlugIn.__init__(self)
self.handlers = {}
@ -94,25 +96,24 @@ class XMPPDispatcher(PlugIn):
return repr(outgoingID)
def dumpHandlers(self):
'''
Return set of user-registered callbacks in it's internal format.
Used within the library to carry user handlers set over Dispatcher
replugins.
'''
"""
Return set of user-registered callbacks in it's internal format. Used
within the library to carry user handlers set over Dispatcher replugins
"""
return self.handlers
def restoreHandlers(self, handlers):
'''
Restores user-registered callbacks structure from dump previously
obtained via dumpHandlers. Used within the library to carry user
handlers set over Dispatcher replugins.
'''
"""
Restore user-registered callbacks structure from dump previously obtained
via dumpHandlers. Used within the library to carry user handlers set over
Dispatcher replugins.
"""
self.handlers = handlers
def _init(self):
'''
Registers default namespaces/protocols/handlers. Used internally.
'''
"""
Register default namespaces/protocols/handlers. Used internally
"""
# FIXME: inject dependencies, do not rely that they are defined by our
# owner
self.RegisterNamespace('unknown')
@ -126,10 +127,10 @@ class XMPPDispatcher(PlugIn):
self.on_responses = {}
def plugin(self, owner):
'''
Plug the Dispatcher instance into Client class instance and send
initial stream header. Used internally.
'''
"""
Plug the Dispatcher instance into Client class instance and send initial
stream header. Used internally
"""
self._init()
self._owner.lastErrNode = None
self._owner.lastErr = None
@ -140,7 +141,9 @@ class XMPPDispatcher(PlugIn):
self.StreamInit()
def plugout(self):
''' Prepares instance to be destructed. '''
"""
Prepare instance to be destructed
"""
self.Stream.dispatch = None
self.Stream.features = None
self.Stream.destroy()
@ -148,7 +151,9 @@ class XMPPDispatcher(PlugIn):
self.Stream = None
def StreamInit(self):
''' Send an initial stream header. '''
"""
Send an initial stream header
"""
self.Stream = simplexml.NodeBuilder()
self.Stream.dispatch = self.dispatch
self.Stream._dispatch_depth = 2
@ -170,15 +175,15 @@ class XMPPDispatcher(PlugIn):
% (tag, ns))
def ProcessNonBlocking(self, data):
'''
Check incoming stream for data waiting.
"""
Check incoming stream for data waiting
:param data: data received from transports/IO sockets
:return:
1) length of processed data if some data were processed;
2) '0' string if no data were processed but link is alive;
3) 0 (zero) if underlying connection is closed.
'''
"""
# FIXME:
# When an error occurs we disconnect the transport directly. Client's
# disconnect method will never be called.
@ -211,41 +216,42 @@ class XMPPDispatcher(PlugIn):
return len(data)
def RegisterNamespace(self, xmlns, order='info'):
'''
Creates internal structures for newly registered namespace.
"""
Create internal structures for newly registered namespace
You can register handlers for this namespace afterwards. By default
one namespace is already registered
(jabber:client or jabber:component:accept depending on context.
'''
"""
log.debug('Registering namespace "%s"' % xmlns)
self.handlers[xmlns] = {}
self.RegisterProtocol('unknown', Protocol, xmlns=xmlns)
self.RegisterProtocol('default', Protocol, xmlns=xmlns)
def RegisterProtocol(self, tag_name, Proto, xmlns=None, order='info'):
'''
Used to declare some top-level stanza name to dispatcher.
Needed to start registering handlers for such stanzas.
"""
Used to declare some top-level stanza name to dispatcher
Iq, message and presence protocols are registered by default.
'''
Needed to start registering handlers for such stanzas. Iq, message and
presence protocols are registered by default.
"""
if not xmlns:
xmlns=self._owner.defaultNamespace
log.debug('Registering protocol "%s" as %s(%s)' %(tag_name, Proto, xmlns))
self.handlers[xmlns][tag_name] = {type:Proto, 'default':[]}
def RegisterNamespaceHandler(self, xmlns, handler, typ='', ns='',
makefirst=0, system=0):
'''
Register handler for processing all stanzas for specified namespace.
'''
makefirst=0, system=0):
"""
Register handler for processing all stanzas for specified namespace
"""
self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst,
system)
def RegisterHandler(self, name, handler, typ='', ns='', xmlns=None,
makefirst=False, system=False):
'''
Register user callback as stanzas handler of declared type.
"""
Register user callback as stanzas handler of declared type
Callback arguments:
dispatcher instance (for replying), incoming return of previous handlers.
@ -263,7 +269,7 @@ class XMPPDispatcher(PlugIn):
and " will be called first nevertheless.
:param system: call handler even if NodeProcessed Exception were raised
already.
'''
"""
if not xmlns:
xmlns=self._owner.defaultNamespace
log.debug('Registering handler %s for "%s" type->%s ns->%s(%s)' %
@ -284,18 +290,20 @@ class XMPPDispatcher(PlugIn):
'system':system})
def RegisterHandlerOnce(self, name, handler, typ='', ns='', xmlns=None,
makefirst=0, system=0):
''' Unregister handler after first call (not implemented yet). '''
makefirst=0, system=0):
"""
Unregister handler after first call (not implemented yet)
"""
# FIXME Drop or implement
if not xmlns:
xmlns = self._owner.defaultNamespace
self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system)
def UnregisterHandler(self, name, handler, typ='', ns='', xmlns=None):
'''
"""
Unregister handler. "typ" and "ns" must be specified exactly the same as
with registering.
'''
"""
if not xmlns:
xmlns = self._owner.defaultNamespace
if not typ and not ns:
@ -314,58 +322,59 @@ class XMPPDispatcher(PlugIn):
pass
def RegisterDefaultHandler(self, handler):
'''
"""
Specify the handler that will be used if no NodeProcessed exception were
raised. This is returnStanzaHandler by default.
'''
"""
self._defaultHandler = handler
def RegisterEventHandler(self, handler):
'''
Register handler that will process events. F.e.
"FILERECEIVED" event. See common/connection: _event_dispatcher()
'''
"""
Register handler that will process events. F.e. "FILERECEIVED" event. See
common/connection: _event_dispatcher()
"""
self._eventHandler = handler
def returnStanzaHandler(self, conn, stanza):
'''
Return stanza back to the sender with <feature-not-implemented/> error set
'''
"""
Return stanza back to the sender with <feature-not-implemented/> error
set
"""
if stanza.getType() in ('get','set'):
conn._owner.send(Error(stanza, ERR_FEATURE_NOT_IMPLEMENTED))
def RegisterCycleHandler(self, handler):
'''
Register handler that will be called on every Dispatcher.Process() call.
'''
"""
Register handler that will be called on every Dispatcher.Process() call
"""
if handler not in self._cycleHandlers:
self._cycleHandlers.append(handler)
def UnregisterCycleHandler(self, handler):
'''
"""
Unregister handler that will is called on every Dispatcher.Process() call
'''
"""
if handler in self._cycleHandlers:
self._cycleHandlers.remove(handler)
def Event(self, realm, event, data):
'''
Raise some event.
"""
Raise some event
:param realm: scope of event. Usually a namespace.
:param event: the event itself. F.e. "SUCCESSFUL SEND".
:param data: data that comes along with event. Depends on event.
'''
"""
if self._eventHandler:
self._eventHandler(realm, event, data)
else:
log.warning('Received unhandled event: %s' % event)
def dispatch(self, stanza, session=None, direct=0):
'''
"""
Main procedure that performs XMPP stanza recognition and calling
apppropriate handlers for it. Called by simplexml.
'''
apppropriate handlers for it. Called by simplexml
"""
# FIXME: Where do we set session and direct. Why? What are those intended
# to do?
@ -450,9 +459,9 @@ class XMPPDispatcher(PlugIn):
self._defaultHandler(session, stanza)
def _WaitForData(self, data):
'''
"""
Internal wrapper around ProcessNonBlocking. Will check for
'''
"""
if data is None:
return
res = self.ProcessNonBlocking(data)
@ -481,12 +490,12 @@ class XMPPDispatcher(PlugIn):
del self._expected[_id]
def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None):
'''
"""
Send stanza and wait for recipient's response to it. Will call transports
on_timeout callback if response is not retrieved in time.
on_timeout callback if response is not retrieved in time
Be aware: Only timeout of latest call of SendAndWait is active.
'''
"""
if timeout is None:
timeout = DEFAULT_TIMEOUT_SECONDS
_waitid = self.send(stanza)
@ -499,15 +508,17 @@ class XMPPDispatcher(PlugIn):
return _waitid
def SendAndCallForResponse(self, stanza, func=None, args=None):
''' Put stanza on the wire and call back when recipient replies.
Additional callback arguments can be specified in args. '''
"""
Put stanza on the wire and call back when recipient replies. Additional
callback arguments can be specified in args
"""
self.SendAndWaitForResponse(stanza, 0, func, args)
def send(self, stanza, now=False):
'''
Wraps transports send method when plugged into NonBlockingClient.
Makes sure stanzas get ID and from tag.
'''
"""
Wrap transports send method when plugged into NonBlockingClient. Makes
sure stanzas get ID and from tag.
"""
ID = None
if type(stanza) not in [type(''), type(u'')]:
if isinstance(stanza, Protocol):
@ -529,7 +540,9 @@ class BOSHDispatcher(XMPPDispatcher):
XMPPDispatcher.PlugIn(self, owner)
def StreamInit(self):
''' Send an initial stream header. '''
"""
Send an initial stream header
"""
self.Stream = simplexml.NodeBuilder()
self.Stream.dispatch = self.dispatch
self.Stream._dispatch_depth = 2
@ -549,7 +562,9 @@ class BOSHDispatcher(XMPPDispatcher):
self._owner.Connection.send_init(after_SASL=self.after_SASL)
def StreamTerminate(self):
''' Send a stream terminator. '''
"""
Send a stream terminator
"""
self._owner.Connection.send_terminator()
def ProcessNonBlocking(self, data=None):

View File

@ -15,10 +15,10 @@
# $Id: features.py,v 1.22 2005/09/30 20:13:04 mikealbon Exp $
'''
"""
Different stuff that wasn't worth separating it into modules
(Registration, Privacy Lists, ...)
'''
"""
from protocol import NS_REGISTER, NS_PRIVACY, NS_DATA, Iq, isResultNode, Node
@ -38,13 +38,13 @@ def _on_default_response(disp, iq, cb):
REGISTER_DATA_RECEIVED = 'REGISTER DATA RECEIVED'
def getRegInfo(disp, host, info={}, sync=True):
'''
Gets registration form from remote host. Info dict can be prefilled
"""
Get registration form from remote host. Info dict can be prefilled
:param disp: plugged dispatcher instance
:param info: dict, like {'username':'joey'}.
See JEP-0077 for details.
'''
"""
iq=Iq('get',NS_REGISTER,to=host)
for i in info.keys():
iq.setTagData(i,info[i])
@ -77,12 +77,12 @@ def _ReceivedRegInfo(con, resp, agent):
con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent,df,False,''))
def register(disp, host, info, cb):
'''
Perform registration on remote server with provided info.
"""
Perform registration on remote server with provided info
If registration fails you can get additional info from the dispatcher's
owner attributes lastErrNode, lastErr and lastErrCode.
'''
"""
iq=Iq('set', NS_REGISTER, to=host)
if not isinstance(info, dict):
info=info.asDict()
@ -91,17 +91,17 @@ def register(disp, host, info, cb):
disp.SendAndCallForResponse(iq, cb)
def unregister(disp, host, cb):
'''
"""
Unregisters with host (permanently removes account). Returns true on success
'''
"""
iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')])
_on_default_response(disp, iq, cb)
def changePasswordTo(disp, newpassword, host=None, cb = None):
'''
Changes password on specified or current (if not specified) server.
Returns true on success.
'''
"""
Changes password on specified or current (if not specified) server. Returns
true on success.
"""
if not host:
host = disp._owner.Server
iq = Iq('set',NS_REGISTER,to=host, payload=[Node('username',
@ -123,10 +123,10 @@ PRIVACY_LIST_RECEIVED = 'PRIVACY LIST RECEIVED'
PRIVACY_LISTS_ACTIVE_DEFAULT = 'PRIVACY LISTS ACTIVE DEFAULT'
def getPrivacyLists(disp):
'''
Requests privacy lists from connected server.
Returns dictionary of existing lists on success.
'''
"""
Request privacy lists from connected server. Returns dictionary of existing
lists on success.
"""
iq = Iq('get', NS_PRIVACY)
def _on_response(resp):
dict_ = {'lists': []}
@ -157,10 +157,10 @@ def getActiveAndDefaultPrivacyLists(disp):
disp.SendAndCallForResponse(iq, _on_response)
def getPrivacyList(disp, listname):
'''
Requests specific privacy list listname. Returns list of XML nodes (rules)
"""
Request specific privacy list listname. Returns list of XML nodes (rules)
taken from the server responce.
'''
"""
def _on_response(resp):
if not isResultNode(resp):
disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False))
@ -170,10 +170,10 @@ def getPrivacyList(disp, listname):
disp.SendAndCallForResponse(iq, _on_response)
def setActivePrivacyList(disp, listname=None, typ='active', cb=None):
'''
Switches privacy list 'listname' to specified type.
By default the type is 'active'. Returns true on success.
'''
"""
Switch privacy list 'listname' to specified type. By default the type is
'active'. Returns true on success.
"""
if listname:
attrs={'name':listname}
else:
@ -182,15 +182,20 @@ def setActivePrivacyList(disp, listname=None, typ='active', cb=None):
_on_default_response(disp, iq, cb)
def setDefaultPrivacyList(disp, listname=None):
''' Sets the default privacy list as 'listname'. Returns true on success. '''
"""
Set the default privacy list as 'listname'. Returns true on success
"""
return setActivePrivacyList(disp, listname,'default')
def setPrivacyList(disp, listname, tags):
'''
Set the ruleset.
"""
Set the ruleset
'list' should be the simpleXML node formatted according to RFC 3921 (XMPP-IM) I.e. Node('list',{'name':listname},payload=[...]). Returns true on success.
'''
'list' should be the simpleXML node formatted according to RFC 3921
(XMPP-IM) I.e. Node('list',{'name':listname},payload=[...]).
Returns true on success.
"""
iq = Iq('set', NS_PRIVACY, xmlns = '')
list_query = iq.getTag('query').setTag('list', {'name': listname})
for item in tags:

View File

@ -12,10 +12,12 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
'''
Idlequeues are Gajim's network heartbeat. Transports can be plugged as
idle objects and be informed about possible IO.
'''
"""
Idlequeues are Gajim's network heartbeat. Transports can be plugged as idle
objects and be informed about possible IO
"""
import os
import select
import logging
@ -45,7 +47,9 @@ IS_CLOSED = 16 # channel closed
def get_idlequeue():
''' Get an appropriate idlequeue '''
"""
Get an appropriate idlequeue
"""
if os.name == 'nt':
# gobject.io_add_watch does not work on windows
return SelectIdleQueue()
@ -59,34 +63,44 @@ def get_idlequeue():
class IdleObject:
'''
Idle listener interface. Listed methods are called by IdleQueue.
'''
"""
Idle listener interface. Listed methods are called by IdleQueue.
"""
def __init__(self):
self.fd = -1 #: filedescriptor, must be unique for each IdleObject
def pollend(self):
''' called on stream failure '''
"""
Called on stream failure
"""
pass
def pollin(self):
''' called on new read event '''
"""
Called on new read event
"""
pass
def pollout(self):
''' called on new write event (connect in sockets is a pollout) '''
"""
Called on new write event (connect in sockets is a pollout)
"""
pass
def read_timeout(self):
''' called when timeout happened '''
"""
Called when timeout happened
"""
pass
class IdleCommand(IdleObject):
'''
"""
Can be subclassed to execute commands asynchronously by the idlequeue.
Result will be optained via file descriptor of created pipe
'''
"""
def __init__(self, on_result):
IdleObject.__init__(self)
# how long (sec.) to wait for result ( 0 - forever )
@ -111,7 +125,9 @@ class IdleCommand(IdleObject):
return ['echo', 'da']
def _compose_command_line(self):
''' return one line representation of command and its arguments '''
"""
Return one line representation of command and its arguments
"""
return reduce(lambda left, right: left + ' ' + right,
self._compose_command_args())
@ -187,7 +203,7 @@ class IdleCommand(IdleObject):
class IdleQueue:
'''
"""
IdleQueue provide three distinct time based features. Uses select.poll()
1. Alarm timeout: Execute a callback after foo seconds
@ -195,7 +211,8 @@ class IdleQueue:
has been set, but not removed in time.
3. Check file descriptor of plugged objects for read, write and error
events
'''
"""
# (timeout, boolean)
# Boolean is True if timeout is specified in seconds, False means miliseconds
PROCESS_TIMEOUT = (200, False)
@ -215,13 +232,15 @@ class IdleQueue:
self._init_idle()
def _init_idle(self):
''' Hook method for subclassed. Will be called by __init__. '''
"""
Hook method for subclassed. Will be called by __init__
"""
self.selector = select.poll()
def set_alarm(self, alarm_cb, seconds):
'''
Sets up a new alarm. alarm_cb will be called after specified seconds.
'''
"""
Set up a new alarm. alarm_cb will be called after specified seconds.
"""
alarm_time = self.current_time() + seconds
# almost impossible, but in case we have another alarm_cb at this time
if alarm_time in self.alarms:
@ -231,10 +250,10 @@ class IdleQueue:
return alarm_time
def remove_alarm(self, alarm_cb, alarm_time):
'''
Removes alarm callback alarm_cb scheduled on alarm_time.
Returns True if it was removed sucessfully, otherwise False
'''
"""
Remove alarm callback alarm_cb scheduled on alarm_time. Returns True if
it was removed sucessfully, otherwise False
"""
if not alarm_time in self.alarms:
return False
i = -1
@ -251,7 +270,9 @@ class IdleQueue:
return False
def remove_timeout(self, fd, timeout=None):
''' Removes the read timeout '''
"""
Remove the read timeout
"""
log.info('read timeout removed for fd %s' % fd)
if fd in self.read_timeouts:
if timeout:
@ -263,12 +284,12 @@ class IdleQueue:
del(self.read_timeouts[fd])
def set_read_timeout(self, fd, seconds, func=None):
'''
Sets a new timeout. If it is not removed after specified seconds,
func or obj.read_timeout() will be called.
"""
Seta a new timeout. If it is not removed after specified seconds,
func or obj.read_timeout() will be called
A filedescriptor fd can have several timeouts.
'''
"""
log_txt = 'read timeout set for fd %s on %s seconds' % (fd, seconds)
if func:
log_txt += ' with function ' + str(func)
@ -280,10 +301,10 @@ class IdleQueue:
self.read_timeouts[fd] = {timeout: func}
def _check_time_events(self):
'''
"""
Execute and remove alarm callbacks and execute func() or read_timeout()
for plugged objects if specified time has ellapsed.
'''
for plugged objects if specified time has ellapsed
"""
log.info('check time evs')
current_time = self.current_time()
@ -313,13 +334,13 @@ class IdleQueue:
del(self.alarms[alarm_time])
def plug_idle(self, obj, writable=True, readable=True):
'''
Plug an IdleObject into idlequeue. Filedescriptor fd must be set.
"""
Plug an IdleObject into idlequeue. Filedescriptor fd must be set
:param obj: the IdleObject
:param writable: True if obj has data to sent
:param readable: True if obj expects data to be reiceived
'''
"""
if obj.fd == -1:
return
if obj.fd in self.queue:
@ -339,11 +360,15 @@ class IdleQueue:
self._add_idle(obj.fd, flags)
def _add_idle(self, fd, flags):
''' Hook method for subclasses, called by plug_idle '''
"""
Hook method for subclasses, called by plug_idle
"""
self.selector.register(fd, flags)
def unplug_idle(self, fd):
''' Removed plugged IdleObject, specified by filedescriptor fd. '''
"""
Remove plugged IdleObject, specified by filedescriptor fd
"""
if fd in self.queue:
del(self.queue[fd])
self._remove_idle(fd)
@ -353,7 +378,9 @@ class IdleQueue:
return time()
def _remove_idle(self, fd):
''' Hook method for subclassed, called by unplug_idle '''
"""
Hook method for subclassed, called by unplug_idle
"""
self.selector.unregister(fd)
def _process_events(self, fd, flags):
@ -379,13 +406,13 @@ class IdleQueue:
return False
def process(self):
'''
Process idlequeue. Check for any pending timeout or alarm events.
Call IdleObjects on possible and requested read, write and error events
on their file descriptors.
"""
Process idlequeue. Check for any pending timeout or alarm events. Call
IdleObjects on possible and requested read, write and error events on
their file descriptors
Call this in regular intervals.
'''
"""
if not self.queue:
# check for timeouts/alert also when there are no active fds
self._check_time_events()
@ -403,24 +430,26 @@ class IdleQueue:
class SelectIdleQueue(IdleQueue):
'''
"""
Extends IdleQueue to use select.select() for polling
This class exisists for the sake of gtk2.8 on windows, which
doesn't seem to support io_add_watch properly (yet)
'''
This class exisists for the sake of gtk2.8 on windows, which doesn't seem to
support io_add_watch properly (yet)
"""
def _init_idle(self):
'''
Creates a dict, which maps file/pipe/sock descriptor to glib event id
'''
"""
Create a dict, which maps file/pipe/sock descriptor to glib event id
"""
self.read_fds = {}
self.write_fds = {}
self.error_fds = {}
def _add_idle(self, fd, flags):
''' this method is called when we plug a new idle object.
Remove descriptor to read/write/error lists, according flags
'''
"""
This method is called when we plug a new idle object. Remove descriptor
to read/write/error lists, according flags
"""
if flags & 3:
self.read_fds[fd] = fd
if flags & 4:
@ -428,9 +457,10 @@ class SelectIdleQueue(IdleQueue):
self.error_fds[fd] = fd
def _remove_idle(self, fd):
''' this method is called when we unplug a new idle object.
Remove descriptor from read/write/error lists
'''
"""
This method is called when we unplug a new idle object. Remove descriptor
from read/write/error lists
"""
if fd in self.read_fds:
del(self.read_fds[fd])
if fd in self.write_fds:
@ -466,27 +496,29 @@ class SelectIdleQueue(IdleQueue):
class GlibIdleQueue(IdleQueue):
'''
Extends IdleQueue to use glib io_add_wath, instead of select/poll
In another 'non gui' implementation of Gajim IdleQueue can be used safetly.
'''
"""
Extends IdleQueue to use glib io_add_wath, instead of select/poll In another
'non gui' implementation of Gajim IdleQueue can be used safetly
"""
# (timeout, boolean)
# Boolean is True if timeout is specified in seconds, False means miliseconds
PROCESS_TIMEOUT = (2, True)
def _init_idle(self):
'''
"""
Creates a dict, which maps file/pipe/sock descriptor to glib event id
'''
"""
self.events = {}
# time() is already called in glib, we just get the last value
# overrides IdleQueue.current_time()
self.current_time = gobject.get_current_time
def _add_idle(self, fd, flags):
''' this method is called when we plug a new idle object.
Start listening for events from fd
'''
"""
This method is called when we plug a new idle object. Start listening for
events from fd
"""
res = gobject.io_add_watch(fd, flags, self._process_events,
priority=gobject.PRIORITY_LOW)
# store the id of the watch, so that we can remove it on unplug
@ -501,9 +533,10 @@ class GlibIdleQueue(IdleQueue):
raise
def _remove_idle(self, fd):
''' this method is called when we unplug a new idle object.
Stop listening for events from fd
'''
"""
This method is called when we unplug a new idle object. Stop listening
for events from fd
"""
if not fd in self.events:
return
gobject.source_remove(self.events[fd])

View File

@ -14,33 +14,34 @@
# $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $
'''
Provides PlugIn class functionality to develop extentions for xmpppy.
'''
"""
Provides PlugIn class functionality to develop extentions for xmpppy
"""
import logging
log = logging.getLogger('gajim.c.x.plugin')
class PlugIn:
'''
"""
Abstract xmpppy plugin infrastructure code, providing plugging in/out and
debugging functionality.
debugging functionality
Inherit to develop pluggable objects. No code change on the owner class
required (the object where we plug into)
For every instance of PlugIn class the 'owner' is the class in what the plug
was plugged.
'''
"""
def __init__(self):
self._exported_methods=[]
def PlugIn(self, owner):
'''
"""
Attach to owner and register ourself and our _exported_methods in it.
If defined by a subclass, call self.plugin(owner) to execute hook
code after plugging.
'''
code after plugging
"""
self._owner=owner
log.info('Plugging %s __INTO__ %s' % (self, self._owner))
if self.__class__.__name__ in owner.__dict__:
@ -63,11 +64,11 @@ class PlugIn:
return self.plugin(owner)
def PlugOut(self):
'''
"""
Unregister our _exported_methods from owner and detach from it.
If defined by a subclass, call self.plugout() after unplugging to execute
hook code.
'''
hook code
"""
log.info('Plugging %s __OUT__ of %s.' % (self, self._owner))
for method in self._exported_methods:
del self._owner.__dict__[method.__name__]
@ -85,13 +86,13 @@ class PlugIn:
@classmethod
def get_instance(cls, *args, **kwargs):
'''
Factory Method for object creation.
"""
Factory Method for object creation
Use this instead of directly initializing the class in order to make
unit testing easier. For testing, this method can be patched to inject
mock objects.
'''
"""
return cls(*args, **kwargs)
# vim: se ts=3:

File diff suppressed because it is too large Load Diff