Move IdleCommand from resolver.py to idlequeue.py

This commit is contained in:
Stephan Erb 2009-01-11 17:54:41 +00:00
parent 0f61260578
commit 541167aa3e
2 changed files with 126 additions and 120 deletions

View File

@ -20,14 +20,10 @@
import sys import sys
import os import os
import re import re
import logging
log = logging.getLogger('gajim.c.resolver')
from xmpp.idlequeue import * from xmpp.idlequeue import IdleCommand
# needed for nslookup
if os.name == 'nt':
from subprocess import * # python24 only. we ask this for Windows
elif os.name == 'posix':
import fcntl
# it is good to check validity of arguments, when calling system commands # it is good to check validity of arguments, when calling system commands
ns_type_pattern = re.compile('^[a-z]+$') ns_type_pattern = re.compile('^[a-z]+$')
@ -35,14 +31,13 @@ ns_type_pattern = re.compile('^[a-z]+$')
# match srv host_name # match srv host_name
host_pattern = re.compile('^[a-z0-9\-._]*[a-z0-9]\.[a-z]{2,}$') host_pattern = re.compile('^[a-z0-9\-._]*[a-z0-9]\.[a-z]{2,}$')
USE_LIBASYNCNS = False
try: try:
#raise ImportError("Manually disabled libasync") #raise ImportError("Manually disabled libasync")
import libasyncns import libasyncns
USE_LIBASYNCNS = True USE_LIBASYNCNS = True
log.info("libasyncns-python loaded") log.info("libasyncns-python loaded")
except ImportError: except ImportError:
USE_LIBASYNCNS = False
log.debug("Import of libasyncns-python failed, getaddrinfo will block", exc_info=True) log.debug("Import of libasyncns-python failed, getaddrinfo will block", exc_info=True)
# FIXME: Remove these prints before release, replace with a warning dialog. # FIXME: Remove these prints before release, replace with a warning dialog.
@ -98,11 +93,11 @@ class CommonResolver():
def start_resolve(self, host, type): def start_resolve(self, host, type):
pass pass
# FIXME: API usage is not consistent! This one requires that process is called
class LibAsyncNSResolver(CommonResolver): class LibAsyncNSResolver(CommonResolver):
''' '''
Asynchronous resolver using libasyncns-python. process() method has to be called Asynchronous resolver using libasyncns-python. process() method has to be
in order to proceed the pending requests. called in order to proceed the pending requests.
Based on patch submitted by Damien Thebault. Based on patch submitted by Damien Thebault.
''' '''
def __init__(self): def __init__(self):
@ -126,7 +121,6 @@ class LibAsyncNSResolver(CommonResolver):
CommonResolver._on_ready(self, host, type, result_list) CommonResolver._on_ready(self, host, type, result_list)
def process(self): def process(self):
try: try:
self.asyncns.wait(False) self.asyncns.wait(False)
@ -157,6 +151,7 @@ class LibAsyncNSResolver(CommonResolver):
resq.userdata['callback'](resq.userdata['dname'], rl) resq.userdata['callback'](resq.userdata['dname'], rl)
return True return True
class NSLookupResolver(CommonResolver): class NSLookupResolver(CommonResolver):
''' '''
Asynchronous DNS resolver calling nslookup. Processing of pending requests Asynchronous DNS resolver calling nslookup. Processing of pending requests
@ -264,112 +259,14 @@ class NSLookupResolver(CommonResolver):
result_list = self.parse_srv_result(host, result) result_list = self.parse_srv_result(host, result)
CommonResolver._on_ready(self, host, type, result_list) CommonResolver._on_ready(self, host, type, result_list)
def start_resolve(self, host, type): def start_resolve(self, host, type):
''' spawn new nslookup process and start waiting for results ''' ''' spawn new nslookup process and start waiting for results '''
ns = NsLookup(self._on_ready, host, type) ns = NsLookup(self._on_ready, host, type)
ns.set_idlequeue(self.idlequeue) ns.set_idlequeue(self.idlequeue)
ns.commandtimeout = 10 ns.commandtimeout = 10
ns.start() ns.start()
# TODO: move IdleCommand class in other file, maybe helpers ?
class IdleCommand(IdleObject):
def __init__(self, on_result):
# how long (sec.) to wait for result ( 0 - forever )
# it is a class var, instead of a constant and we can override it.
self.commandtimeout = 0
# when we have some kind of result (valid, ot not) we call this handler
self.result_handler = on_result
# if it is True, we can safetely execute the command
self.canexecute = True
self.idlequeue = None
self.result =''
def set_idlequeue(self, idlequeue):
self.idlequeue = idlequeue
def _return_result(self):
if self.result_handler:
self.result_handler(self.result)
self.result_handler = None
def _compose_command_args(self):
return ['echo', 'da']
def _compose_command_line(self):
''' return one line representation of command and its arguments '''
return reduce(lambda left, right: left + ' ' + right, self._compose_command_args())
def wait_child(self):
if self.pipe.poll() is None:
# result timeout
if self.endtime < self.idlequeue.current_time():
self._return_result()
self.pipe.stdout.close()
self.pipe.stdin.close()
else:
# child is still active, continue to wait
self.idlequeue.set_alarm(self.wait_child, 0.1)
else:
# child has quit
self.result = self.pipe.stdout.read()
self._return_result()
self.pipe.stdout.close()
self.pipe.stdin.close()
def start(self):
if not self.canexecute:
self.result = ''
self._return_result()
return
if os.name == 'nt':
self._start_nt()
elif os.name == 'posix':
self._start_posix()
def _start_nt(self):
# if gajim is started from noninteraactive shells stdin is closed and
# cannot be forwarded, so we have to keep it open
self.pipe = Popen(self._compose_command_args(), stdout=PIPE,
bufsize = 1024, shell = True, stderr = STDOUT, stdin = PIPE)
if self.commandtimeout >= 0:
self.endtime = self.idlequeue.current_time() + self.commandtimeout
self.idlequeue.set_alarm(self.wait_child, 0.1)
def _start_posix(self):
self.pipe = os.popen(self._compose_command_line())
self.fd = self.pipe.fileno()
fcntl.fcntl(self.pipe, fcntl.F_SETFL, os.O_NONBLOCK)
self.idlequeue.plug_idle(self, False, True)
if self.commandtimeout >= 0:
self.idlequeue.set_read_timeout(self.fd, self.commandtimeout)
def end(self):
self.idlequeue.unplug_idle(self.fd)
try:
self.pipe.close()
except:
pass
def pollend(self):
self.idlequeue.remove_timeout(self.fd)
self.end()
self._return_result()
def pollin(self):
try:
res = self.pipe.read()
except Exception, e:
res = ''
if res == '':
return self.pollend()
else:
self.result += res
def read_timeout(self):
self.end()
self._return_result()
class NsLookup(IdleCommand): class NsLookup(IdleCommand):
def __init__(self, on_result, host='_xmpp-client', type='srv'): def __init__(self, on_result, host='_xmpp-client', type='srv'):
IdleCommand.__init__(self, on_result) IdleCommand.__init__(self, on_result)
@ -378,11 +275,11 @@ class NsLookup(IdleCommand):
self.type = type.lower() self.type = type.lower()
if not host_pattern.match(self.host): if not host_pattern.match(self.host):
# invalid host name # invalid host name
print >> sys.stderr, 'Invalid host: %s' % self.host log.error('Invalid host: %s' % self.host)
self.canexecute = False self.canexecute = False
return return
if not ns_type_pattern.match(self.type): if not ns_type_pattern.match(self.type):
print >> sys.stderr, 'Invalid querytype: %s' % self.type log.error('Invalid querytype: %s' % self.type)
self.canexecute = False self.canexecute = False
return return
@ -396,15 +293,12 @@ class NsLookup(IdleCommand):
# below lines is on how to use API and assist in testing # below lines is on how to use API and assist in testing
if __name__ == '__main__': if __name__ == '__main__':
if os.name == 'posix':
idlequeue = IdleQueue()
elif os.name == 'nt':
idlequeue = SelectIdleQueue()
# testing Resolver class
import gobject import gobject
import gtk import gtk
from xmpp import idlequeue
resolver = Resolver(idlequeue) idlequeue = idlequeue.get_idlequeue()
resolver = get_resolver(idlequeue)
def clicked(widget): def clicked(widget):
global resolver global resolver

View File

@ -20,12 +20,19 @@ import os
import select import select
import logging import logging
log = logging.getLogger('gajim.c.x.idlequeue') log = logging.getLogger('gajim.c.x.idlequeue')
# needed for get_idleqeue
try: try:
import gobject import gobject
HAVE_GOBJECT = True HAVE_GOBJECT = True
except ImportError: except ImportError:
HAVE_GOBJECT = False HAVE_GOBJECT = False
# needed for idlecommand
if os.name == 'nt':
from subprocess import * # python24 only. we ask this for Windows
elif os.name == 'posix':
import fcntl
FLAG_WRITE = 20 # write only FLAG_WRITE = 20 # write only
FLAG_READ = 19 # read only FLAG_READ = 19 # read only
@ -36,6 +43,7 @@ PENDING_READ = 3 # waiting read event
PENDING_WRITE = 4 # waiting write event PENDING_WRITE = 4 # waiting write event
IS_CLOSED = 16 # channel closed IS_CLOSED = 16 # channel closed
def get_idlequeue(): def get_idlequeue():
''' Get an appropriate idlequeue ''' ''' Get an appropriate idlequeue '''
if os.name == 'nt': if os.name == 'nt':
@ -74,6 +82,110 @@ class IdleObject:
pass 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 )
# it is a class var, instead of a constant and we can override it.
self.commandtimeout = 0
# when we have some kind of result (valid, ot not) we call this handler
self.result_handler = on_result
# if it is True, we can safetely execute the command
self.canexecute = True
self.idlequeue = None
self.result =''
def set_idlequeue(self, idlequeue):
self.idlequeue = idlequeue
def _return_result(self):
if self.result_handler:
self.result_handler(self.result)
self.result_handler = None
def _compose_command_args(self):
return ['echo', 'da']
def _compose_command_line(self):
''' return one line representation of command and its arguments '''
return reduce(lambda left, right: left + ' ' + right,
self._compose_command_args())
def wait_child(self):
if self.pipe.poll() is None:
# result timeout
if self.endtime < self.idlequeue.current_time():
self._return_result()
self.pipe.stdout.close()
self.pipe.stdin.close()
else:
# child is still active, continue to wait
self.idlequeue.set_alarm(self.wait_child, 0.1)
else:
# child has quit
self.result = self.pipe.stdout.read()
self._return_result()
self.pipe.stdout.close()
self.pipe.stdin.close()
def start(self):
if not self.canexecute:
self.result = ''
self._return_result()
return
if os.name == 'nt':
self._start_nt()
elif os.name == 'posix':
self._start_posix()
def _start_nt(self):
# if gajim is started from noninteraactive shells stdin is closed and
# cannot be forwarded, so we have to keep it open
self.pipe = Popen(self._compose_command_args(), stdout=PIPE,
bufsize = 1024, shell = True, stderr = STDOUT, stdin = PIPE)
if self.commandtimeout >= 0:
self.endtime = self.idlequeue.current_time() + self.commandtimeout
self.idlequeue.set_alarm(self.wait_child, 0.1)
def _start_posix(self):
self.pipe = os.popen(self._compose_command_line())
self.fd = self.pipe.fileno()
fcntl.fcntl(self.pipe, fcntl.F_SETFL, os.O_NONBLOCK)
self.idlequeue.plug_idle(self, False, True)
if self.commandtimeout >= 0:
self.idlequeue.set_read_timeout(self.fd, self.commandtimeout)
def end(self):
self.idlequeue.unplug_idle(self.fd)
try:
self.pipe.close()
except:
pass
def pollend(self):
self.idlequeue.remove_timeout(self.fd)
self.end()
self._return_result()
def pollin(self):
try:
res = self.pipe.read()
except Exception, e:
res = ''
if res == '':
return self.pollend()
else:
self.result += res
def read_timeout(self):
self.end()
self._return_result()
class IdleQueue: class IdleQueue:
''' '''
IdleQueue provide three distinct time based features. Uses select.poll() IdleQueue provide three distinct time based features. Uses select.poll()