From montanaro at users.sourceforge.net Sat Feb 5 17:22:39 2011 From: montanaro at users.sourceforge.net (montanaro at users.sourceforge.net) Date: Sat, 05 Feb 2011 16:22:39 +0000 Subject: [Spambayes-checkins] SF.net SVN: spambayes:[3270] trunk/spambayes/scripts/sb_server.py Message-ID: Revision: 3270 http://spambayes.svn.sourceforge.net/spambayes/?rev=3270&view=rev Author: montanaro Date: 2011-02-05 16:22:38 +0000 (Sat, 05 Feb 2011) Log Message: ----------- Seems like an obvious bug. Modified Paths: -------------- trunk/spambayes/scripts/sb_server.py Modified: trunk/spambayes/scripts/sb_server.py =================================================================== --- trunk/spambayes/scripts/sb_server.py 2010-12-02 15:23:44 UTC (rev 3269) +++ trunk/spambayes/scripts/sb_server.py 2011-02-05 16:22:38 UTC (rev 3270) @@ -974,6 +974,7 @@ # affect any running proxies - once a listener has created a proxy, # that proxy is then independent of it. # (but won't closing the database screw them?) + global state for proxy in proxyListeners: proxy.close() del proxyListeners[:] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. From montanaro at users.sourceforge.net Fri Feb 25 21:07:51 2011 From: montanaro at users.sourceforge.net (montanaro at users.sourceforge.net) Date: Fri, 25 Feb 2011 20:07:51 +0000 Subject: [Spambayes-checkins] SF.net SVN: spambayes:[3272] trunk/spambayes/spambayes/__init__.py Message-ID: Revision: 3272 http://spambayes.svn.sourceforge.net/spambayes/?rev=3272&view=rev Author: montanaro Date: 2011-02-25 20:07:51 +0000 (Fri, 25 Feb 2011) Log Message: ----------- bump version Modified Paths: -------------- trunk/spambayes/spambayes/__init__.py Modified: trunk/spambayes/spambayes/__init__.py =================================================================== --- trunk/spambayes/spambayes/__init__.py 2011-02-25 20:05:02 UTC (rev 3271) +++ trunk/spambayes/spambayes/__init__.py 2011-02-25 20:07:51 UTC (rev 3272) @@ -1,4 +1,4 @@ # package marker. -__version__ = "1.1a6" -__date__ = "April 1, 2010" +__version__ = "1.1b1" +__date__ = "February, 25, 2011" This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. From montanaro at users.sourceforge.net Fri Feb 25 21:05:02 2011 From: montanaro at users.sourceforge.net (montanaro at users.sourceforge.net) Date: Fri, 25 Feb 2011 20:05:02 +0000 Subject: [Spambayes-checkins] SF.net SVN: spambayes:[3271] trunk/spambayes Message-ID: Revision: 3271 http://spambayes.svn.sourceforge.net/spambayes/?rev=3271&view=rev Author: montanaro Date: 2011-02-25 20:05:02 +0000 (Fri, 25 Feb 2011) Log Message: ----------- Modified Paths: -------------- trunk/spambayes/spambayes/Dibbler.py trunk/spambayes/spambayes/test/test_sb_imapfilter.py trunk/spambayes/spambayes/test/test_sb_pop3dnd.py trunk/spambayes/spambayes/test/test_sb_server.py trunk/spambayes/spambayes/test/test_smtpproxy.py Added Paths: ----------- trunk/spambayes/spambayes/asynchat.py trunk/spambayes/spambayes/asyncore.py trunk/spambayes/tox.ini Modified: trunk/spambayes/spambayes/Dibbler.py =================================================================== --- trunk/spambayes/spambayes/Dibbler.py 2011-02-05 16:22:38 UTC (rev 3270) +++ trunk/spambayes/spambayes/Dibbler.py 2011-02-25 20:05:02 UTC (rev 3271) @@ -170,7 +170,7 @@ import StringIO import sys, re, time, traceback, base64 -import socket, asyncore, asynchat, cgi, urlparse, webbrowser +import socket, cgi, urlparse, webbrowser try: "".rstrip("abc") @@ -183,6 +183,7 @@ RSTRIP_CHARS_AVAILABLE = True from spambayes.port import md5 +from spambayes import asyncore, asynchat class BrighterAsyncChat(asynchat.async_chat): """An asynchat.async_chat that doesn't give spurious warnings on Added: trunk/spambayes/spambayes/asynchat.py =================================================================== --- trunk/spambayes/spambayes/asynchat.py (rev 0) +++ trunk/spambayes/spambayes/asynchat.py 2011-02-25 20:05:02 UTC (rev 3271) @@ -0,0 +1,295 @@ +# -*- Mode: Python; tab-width: 4 -*- +# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp +# Author: Sam Rushing + +# ====================================================================== +# Copyright 1996 by Sam Rushing +# +# All Rights Reserved +# +# Permission to use, copy, modify, and distribute this software and +# its documentation for any purpose and without fee is hereby +# granted, provided that the above copyright notice appear in all +# copies and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of Sam +# Rushing not be used in advertising or publicity pertaining to +# distribution of the software without specific, written prior +# permission. +# +# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN +# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# ====================================================================== + +r"""A class supporting chat-style (command/response) protocols. + +This class adds support for 'chat' style protocols - where one side +sends a 'command', and the other sends a response (examples would be +the common internet protocols - smtp, nntp, ftp, etc..). + +The handle_read() method looks at the input stream for the current +'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n' +for multi-line output), calling self.found_terminator() on its +receipt. + +for example: +Say you build an async nntp client using this class. At the start +of the connection, you'll have self.terminator set to '\r\n', in +order to process the single-line greeting. Just before issuing a +'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST +command will be accumulated (using your own 'collect_incoming_data' +method) up to the terminator, and then control will be returned to +you - by calling your self.found_terminator() method. +""" + +import socket +from spambayes import asyncore +from collections import deque + +class async_chat (asyncore.dispatcher): + """This is an abstract class. You must derive from this class, and add + the two methods collect_incoming_data() and found_terminator()""" + + # these are overridable defaults + + ac_in_buffer_size = 4096 + ac_out_buffer_size = 4096 + + def __init__ (self, conn=None): + self.ac_in_buffer = '' + self.ac_out_buffer = '' + self.producer_fifo = fifo() + asyncore.dispatcher.__init__ (self, conn) + + def collect_incoming_data(self, data): + raise NotImplementedError, "must be implemented in subclass" + + def found_terminator(self): + raise NotImplementedError, "must be implemented in subclass" + + def set_terminator (self, term): + "Set the input delimiter. Can be a fixed string of any length, an integer, or None" + self.terminator = term + + def get_terminator (self): + return self.terminator + + # grab some more data from the socket, + # throw it to the collector method, + # check for the terminator, + # if found, transition to the next state. + + def handle_read (self): + + try: + data = self.recv (self.ac_in_buffer_size) + except socket.error, why: + self.handle_error() + return + + self.ac_in_buffer = self.ac_in_buffer + data + + # Continue to search for self.terminator in self.ac_in_buffer, + # while calling self.collect_incoming_data. The while loop + # is necessary because we might read several data+terminator + # combos with a single recv(1024). + + while self.ac_in_buffer: + lb = len(self.ac_in_buffer) + terminator = self.get_terminator() + if not terminator: + # no terminator, collect it all + self.collect_incoming_data (self.ac_in_buffer) + self.ac_in_buffer = '' + elif isinstance(terminator, int) or isinstance(terminator, long): + # numeric terminator + n = terminator + if lb < n: + self.collect_incoming_data (self.ac_in_buffer) + self.ac_in_buffer = '' + self.terminator = self.terminator - lb + else: + self.collect_incoming_data (self.ac_in_buffer[:n]) + self.ac_in_buffer = self.ac_in_buffer[n:] + self.terminator = 0 + self.found_terminator() + else: + # 3 cases: + # 1) end of buffer matches terminator exactly: + # collect data, transition + # 2) end of buffer matches some prefix: + # collect data to the prefix + # 3) end of buffer does not match any prefix: + # collect data + terminator_len = len(terminator) + index = self.ac_in_buffer.find(terminator) + if index != -1: + # we found the terminator + if index > 0: + # don't bother reporting the empty string (source of subtle bugs) + self.collect_incoming_data (self.ac_in_buffer[:index]) + self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:] + # This does the Right Thing if the terminator is changed here. + self.found_terminator() + else: + # check for a prefix of the terminator + index = find_prefix_at_end (self.ac_in_buffer, terminator) + if index: + if index != lb: + # we found a prefix, collect up to the prefix + self.collect_incoming_data (self.ac_in_buffer[:-index]) + self.ac_in_buffer = self.ac_in_buffer[-index:] + break + else: + # no prefix, collect it all + self.collect_incoming_data (self.ac_in_buffer) + self.ac_in_buffer = '' + + def handle_write (self): + self.initiate_send () + + def handle_close (self): + self.close() + + def push (self, data): + self.producer_fifo.push (simple_producer (data)) + self.initiate_send() + + def push_with_producer (self, producer): + self.producer_fifo.push (producer) + self.initiate_send() + + def readable (self): + "predicate for inclusion in the readable for select()" + return (len(self.ac_in_buffer) <= self.ac_in_buffer_size) + + def writable (self): + "predicate for inclusion in the writable for select()" + # return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected) + # this is about twice as fast, though not as clear. + return not ( + (self.ac_out_buffer == '') and + self.producer_fifo.is_empty() and + self.connected + ) + + def close_when_done (self): + "automatically close this channel once the outgoing queue is empty" + self.producer_fifo.push (None) + + # refill the outgoing buffer by calling the more() method + # of the first producer in the queue + def refill_buffer (self): + while 1: + if len(self.producer_fifo): + p = self.producer_fifo.first() + # a 'None' in the producer fifo is a sentinel, + # telling us to close the channel. + if p is None: + if not self.ac_out_buffer: + self.producer_fifo.pop() + self.close() + return + elif isinstance(p, str): + self.producer_fifo.pop() + self.ac_out_buffer = self.ac_out_buffer + p + return + data = p.more() + if data: + self.ac_out_buffer = self.ac_out_buffer + data + return + else: + self.producer_fifo.pop() + else: + return + + def initiate_send (self): + obs = self.ac_out_buffer_size + # try to refill the buffer + if (len (self.ac_out_buffer) < obs): + self.refill_buffer() + + if self.ac_out_buffer and self.connected: + # try to send the buffer + try: + num_sent = self.send (self.ac_out_buffer[:obs]) + if num_sent: + self.ac_out_buffer = self.ac_out_buffer[num_sent:] + + except socket.error, why: + self.handle_error() + return + + def discard_buffers (self): + # Emergencies only! + self.ac_in_buffer = '' + self.ac_out_buffer = '' + while self.producer_fifo: + self.producer_fifo.pop() + + +class simple_producer: + + def __init__ (self, data, buffer_size=512): + self.data = data + self.buffer_size = buffer_size + + def more (self): + if len (self.data) > self.buffer_size: + result = self.data[:self.buffer_size] + self.data = self.data[self.buffer_size:] + return result + else: + result = self.data + self.data = '' + return result + +class fifo: + def __init__ (self, list=None): + if not list: + self.list = deque() + else: + self.list = deque(list) + + def __len__ (self): + return len(self.list) + + def is_empty (self): + return not self.list + + def first (self): + return self.list[0] + + def push (self, data): + self.list.append(data) + + def pop (self): + if self.list: + return (1, self.list.popleft()) + else: + return (0, None) + +# Given 'haystack', see if any prefix of 'needle' is at its end. This +# assumes an exact match has already been checked. Return the number of +# characters matched. +# for example: +# f_p_a_e ("qwerty\r", "\r\n") => 1 +# f_p_a_e ("qwertydkjf", "\r\n") => 0 +# f_p_a_e ("qwerty\r\n", "\r\n") => + +# this could maybe be made faster with a computed regex? +# [answer: no; circa Python-2.0, Jan 2001] +# new python: 28961/s +# old python: 18307/s +# re: 12820/s +# regex: 14035/s + +def find_prefix_at_end (haystack, needle): + l = len(needle) - 1 + while l and not haystack.endswith(needle[:l]): + l -= 1 + return l Property changes on: trunk/spambayes/spambayes/asynchat.py ___________________________________________________________________ Added: svn:eol-style + native Added: trunk/spambayes/spambayes/asyncore.py =================================================================== --- trunk/spambayes/spambayes/asyncore.py (rev 0) +++ trunk/spambayes/spambayes/asyncore.py 2011-02-25 20:05:02 UTC (rev 3271) @@ -0,0 +1,551 @@ +# -*- Mode: Python -*- +# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp +# Author: Sam Rushing + +# ====================================================================== +# Copyright 1996 by Sam Rushing +# +# All Rights Reserved +# +# Permission to use, copy, modify, and distribute this software and +# its documentation for any purpose and without fee is hereby +# granted, provided that the above copyright notice appear in all +# copies and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of Sam +# Rushing not be used in advertising or publicity pertaining to +# distribution of the software without specific, written prior +# permission. +# +# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN +# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# ====================================================================== + +"""Basic infrastructure for asynchronous socket service clients and servers. + +There are only two ways to have a program on a single processor do "more +than one thing at a time". Multi-threaded programming is the simplest and +most popular way to do it, but there is another very different technique, +that lets you have nearly all the advantages of multi-threading, without +actually using multiple threads. it's really only practical if your program +is largely I/O bound. If your program is CPU bound, then pre-emptive +scheduled threads are probably what you really need. Network servers are +rarely CPU-bound, however. + +If your operating system supports the select() system call in its I/O +library (and nearly all do), then you can use it to juggle multiple +communication channels at once; doing other work while your I/O is taking +place in the "background." Although this strategy can seem strange and +complex, especially at first, it is in many ways easier to understand and +control than multi-threaded programming. The module documented here solves +many of the difficult problems for you, making the task of building +sophisticated high-performance network servers and clients a snap. +""" + +import select +import socket +import sys +import time + +import os +from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \ + ENOTCONN, ESHUTDOWN, EINTR, EISCONN, errorcode + +try: + socket_map +except NameError: + socket_map = {} + +class ExitNow(Exception): + pass + +def read(obj): + try: + obj.handle_read_event() + except ExitNow: + raise + except: + obj.handle_error() + +def write(obj): + try: + obj.handle_write_event() + except ExitNow: + raise + except: + obj.handle_error() + +def _exception (obj): + try: + obj.handle_expt_event() + except ExitNow: + raise + except: + obj.handle_error() + +def readwrite(obj, flags): + try: + if flags & (select.POLLIN | select.POLLPRI): + obj.handle_read_event() + if flags & select.POLLOUT: + obj.handle_write_event() + if flags & (select.POLLERR | select.POLLHUP | select.POLLNVAL): + obj.handle_expt_event() + except ExitNow: + raise + except: + obj.handle_error() + +def poll(timeout=0.0, map=None): + if map is None: + map = socket_map + if map: + r = []; w = []; e = [] + for fd, obj in map.items(): + is_r = obj.readable() + is_w = obj.writable() + if is_r: + r.append(fd) + if is_w: + w.append(fd) + if is_r or is_w: + e.append(fd) + if [] == r == w == e: + time.sleep(timeout) + else: + try: + r, w, e = select.select(r, w, e, timeout) + except select.error, err: + if err[0] != EINTR: + raise + else: + return + + for fd in r: + obj = map.get(fd) + if obj is None: + continue + read(obj) + + for fd in w: + obj = map.get(fd) + if obj is None: + continue + write(obj) + + for fd in e: + obj = map.get(fd) + if obj is None: + continue + _exception(obj) + +def poll2(timeout=0.0, map=None): + # Use the poll() support added to the select module in Python 2.0 + if map is None: + map = socket_map + if timeout is not None: + # timeout is in milliseconds + timeout = int(timeout*1000) + pollster = select.poll() + if map: + for fd, obj in map.items(): + flags = 0 + if obj.readable(): + flags |= select.POLLIN | select.POLLPRI + if obj.writable(): + flags |= select.POLLOUT + if flags: + # Only check for exceptions if object was either readable + # or writable. + flags |= select.POLLERR | select.POLLHUP | select.POLLNVAL + pollster.register(fd, flags) + try: + r = pollster.poll(timeout) + except select.error, err: + if err[0] != EINTR: + raise + r = [] + for fd, flags in r: + obj = map.get(fd) + if obj is None: + continue + readwrite(obj, flags) + +poll3 = poll2 # Alias for backward compatibility + +def loop(timeout=30.0, use_poll=False, map=None, count=None): + if map is None: + map = socket_map + + if use_poll and hasattr(select, 'poll'): + poll_fun = poll2 + else: + poll_fun = poll + + if count is None: + while map: + poll_fun(timeout, map) + + else: + while map and count > 0: + poll_fun(timeout, map) + count = count - 1 + +class dispatcher: + + debug = False + connected = False + accepting = False + closing = False + addr = None + + def __init__(self, sock=None, map=None): + if map is None: + self._map = socket_map + else: + self._map = map + + if sock: + self.set_socket(sock, map) + # I think it should inherit this anyway + self.socket.setblocking(0) + self.connected = True + # XXX Does the constructor require that the socket passed + # be connected? + try: + self.addr = sock.getpeername() + except socket.error: + # The addr isn't crucial + pass + else: + self.socket = None + + def __repr__(self): + status = [self.__class__.__module__+"."+self.__class__.__name__] + if self.accepting and self.addr: + status.append('listening') + elif self.connected: + status.append('connected') + if self.addr is not None: + try: + status.append('%s:%d' % self.addr) + except TypeError: + status.append(repr(self.addr)) + return '<%s at %#x>' % (' '.join(status), id(self)) + + def add_channel(self, map=None): + #self.log_info('adding channel %s' % self) + if map is None: + map = self._map + map[self._fileno] = self + + def del_channel(self, map=None): + fd = self._fileno + if map is None: + map = self._map + if map.has_key(fd): + #self.log_info('closing channel %d:%s' % (fd, self)) + del map[fd] + self._fileno = None + + def create_socket(self, family, type): + self.family_and_type = family, type + self.socket = socket.socket(family, type) + self.socket.setblocking(0) + self._fileno = self.socket.fileno() + self.add_channel() + + def set_socket(self, sock, map=None): + self.socket = sock +## self.__dict__['socket'] = sock + self._fileno = sock.fileno() + self.add_channel(map) + + def set_reuse_addr(self): + # try to re-use a server port if possible + try: + self.socket.setsockopt( + socket.SOL_SOCKET, socket.SO_REUSEADDR, + self.socket.getsockopt(socket.SOL_SOCKET, + socket.SO_REUSEADDR) | 1 + ) + except socket.error: + pass + + # ================================================== + # predicates for select() + # these are used as filters for the lists of sockets + # to pass to select(). + # ================================================== + + def readable(self): + return True + + def writable(self): + return True + + # ================================================== + # socket object methods. + # ================================================== + + def listen(self, num): + self.accepting = True + if os.name == 'nt' and num > 5: + num = 1 + return self.socket.listen(num) + + def bind(self, addr): + self.addr = addr + return self.socket.bind(addr) + + def connect(self, address): + self.connected = False + err = self.socket.connect_ex(address) + # XXX Should interpret Winsock return values + if err in (EINPROGRESS, EALREADY, EWOULDBLOCK): + return + if err in (0, EISCONN): + self.addr = address + self.connected = True + self.handle_connect() + else: + raise socket.error, (err, errorcode[err]) + + def accept(self): + # XXX can return either an address pair or None + try: + conn, addr = self.socket.accept() + return conn, addr + except socket.error, why: + if why[0] == EWOULDBLOCK: + pass + else: + raise + + def send(self, data): + try: + result = self.socket.send(data) + return result + except socket.error, why: + if why[0] == EWOULDBLOCK: + return 0 + else: + raise + return 0 + + def recv(self, buffer_size): + try: + data = self.socket.recv(buffer_size) + if not data: + # a closed connection is indicated by signaling + # a read condition, and having recv() return 0. + self.handle_close() + return '' + else: + return data + except socket.error, why: + # winsock sometimes throws ENOTCONN + if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]: + self.handle_close() + return '' + else: + raise + + def close(self): + self.del_channel() + self.socket.close() + + # cheap inheritance, used to pass all other attribute + # references to the underlying socket object. + def __getattr__(self, attr): + return getattr(self.socket, attr) + + # log and log_info may be overridden to provide more sophisticated + # logging and warning methods. In general, log is for 'hit' logging + # and 'log_info' is for informational, warning and error logging. + + def log(self, message): + sys.stderr.write('log: %s\n' % str(message)) + + def log_info(self, message, type='info'): + if __debug__ or type != 'info': + print '%s: %s' % (type, message) + + def handle_read_event(self): + if self.accepting: + # for an accepting socket, getting a read implies + # that we are connected + if not self.connected: + self.connected = True + self.handle_accept() + elif not self.connected: + self.handle_connect() + self.connected = True + self.handle_read() + else: + self.handle_read() + + def handle_write_event(self): + # getting a write implies that we are connected + if not self.connected: + self.handle_connect() + self.connected = True + self.handle_write() + + def handle_expt_event(self): + self.handle_expt() + + def handle_error(self): + nil, t, v, tbinfo = compact_traceback() + + # sometimes a user repr method will crash. + try: + self_repr = repr(self) + except: + self_repr = '<__repr__(self) failed for object at %0x>' % id(self) + + self.log_info( + 'uncaptured python exception, closing channel %s (%s:%s %s)' % ( + self_repr, + t, + v, + tbinfo + ), + 'error' + ) + self.close() + + def handle_expt(self): + self.log_info('unhandled exception', 'warning') + + def handle_read(self): + self.log_info('unhandled read event', 'warning') + + def handle_write(self): + self.log_info('unhandled write event', 'warning') + + def handle_connect(self): + self.log_info('unhandled connect event', 'warning') + + def handle_accept(self): + self.log_info('unhandled accept event', 'warning') + + def handle_close(self): + self.log_info('unhandled close event', 'warning') + self.close() + +# --------------------------------------------------------------------------- +# adds simple buffered output capability, useful for simple clients. +# [for more sophisticated usage use asynchat.async_chat] +# --------------------------------------------------------------------------- + +class dispatcher_with_send(dispatcher): + + def __init__(self, sock=None, map=None): + dispatcher.__init__(self, sock, map) + self.out_buffer = '' + + def initiate_send(self): + num_sent = 0 + num_sent = dispatcher.send(self, self.out_buffer[:512]) + self.out_buffer = self.out_buffer[num_sent:] + + def handle_write(self): + self.initiate_send() + + def writable(self): + return (not self.connected) or len(self.out_buffer) + + def send(self, data): + if self.debug: + self.log_info('sending %s' % repr(data)) + self.out_buffer = self.out_buffer + data + self.initiate_send() + +# --------------------------------------------------------------------------- +# used for debugging. +# --------------------------------------------------------------------------- + +def compact_traceback(): + t, v, tb = sys.exc_info() + tbinfo = [] + assert tb # Must have a traceback + while tb: + tbinfo.append(( + tb.tb_frame.f_code.co_filename, + tb.tb_frame.f_code.co_name, + str(tb.tb_lineno) + )) + tb = tb.tb_next + + # just to be safe + del tb + + file, function, line = tbinfo[-1] + info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo]) + return (file, function, line), t, v, info + +def close_all(map=None): + if map is None: + map = socket_map + for x in map.values(): + x.socket.close() + map.clear() + +# Asynchronous File I/O: +# +# After a little research (reading man pages on various unixen, and +# digging through the linux kernel), I've determined that select() +# isn't meant for doing asynchronous file i/o. +# Heartening, though - reading linux/mm/filemap.c shows that linux +# supports asynchronous read-ahead. So _MOST_ of the time, the data +# will be sitting in memory for us already when we go to read it. +# +# What other OS's (besides NT) support async file i/o? [VMS?] +# +# Regardless, this is useful for pipes, and stdin/stdout... + +if os.name == 'posix': + import fcntl + + class file_wrapper: + # here we override just enough to make a file + # look like a socket for the purposes of asyncore. + + def __init__(self, fd): + self.fd = fd + + def recv(self, *args): + return os.read(self.fd, *args) + + def send(self, *args): + return os.write(self.fd, *args) + + read = recv + write = send + + def close(self): + os.close(self.fd) + + def fileno(self): + return self.fd + + class file_dispatcher(dispatcher): + + def __init__(self, fd, map=None): + dispatcher.__init__(self, None, map) + self.connected = True + self.set_file(fd) + # set it to non-blocking mode + flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0) + flags = flags | os.O_NONBLOCK + fcntl.fcntl(fd, fcntl.F_SETFL, flags) + + def set_file(self, fd): + self._fileno = fd + self.socket = file_wrapper(fd) + self.add_channel() Property changes on: trunk/spambayes/spambayes/asyncore.py ___________________________________________________________________ Added: svn:eol-style + native Modified: trunk/spambayes/spambayes/test/test_sb_imapfilter.py =================================================================== --- trunk/spambayes/spambayes/test/test_sb_imapfilter.py 2011-02-05 16:22:38 UTC (rev 3270) +++ trunk/spambayes/spambayes/test/test_sb_imapfilter.py 2011-02-25 20:05:02 UTC (rev 3271) @@ -9,7 +9,6 @@ import threading import imaplib import unittest -import asyncore import StringIO try: @@ -22,6 +21,7 @@ from spambayes import message from spambayes import Dibbler +from spambayes import asyncore from spambayes.Options import options from spambayes.classifier import Classifier from sb_imapfilter import run, BadIMAPResponseError, LoginFailure Modified: trunk/spambayes/spambayes/test/test_sb_pop3dnd.py =================================================================== --- trunk/spambayes/spambayes/test/test_sb_pop3dnd.py 2011-02-05 16:22:38 UTC (rev 3270) +++ trunk/spambayes/spambayes/test/test_sb_pop3dnd.py 2011-02-25 20:05:02 UTC (rev 3271) @@ -218,7 +218,7 @@ if __name__=='__main__': def runTestServer(): - import asyncore + from spambayes import asyncore asyncore.loop() TestListener() thread.start_new_thread(runTestServer, ()) Modified: trunk/spambayes/spambayes/test/test_sb_server.py =================================================================== --- trunk/spambayes/spambayes/test/test_sb_server.py 2011-02-05 16:22:38 UTC (rev 3270) +++ trunk/spambayes/spambayes/test/test_sb_server.py 2011-02-25 20:05:02 UTC (rev 3271) @@ -76,7 +76,6 @@ malformed1 = """From: ta-meyer at ihug.co.nz Subject: No body, and no separator""" -import asyncore import socket import operator import re @@ -87,6 +86,7 @@ import sb_test_support sb_test_support.fix_sys_path() +from spambayes import asyncore from spambayes import Dibbler from spambayes import tokenizer from spambayes.UserInterface import UserInterfaceServer Modified: trunk/spambayes/spambayes/test/test_smtpproxy.py =================================================================== --- trunk/spambayes/spambayes/test/test_smtpproxy.py 2011-02-05 16:22:38 UTC (rev 3270) +++ trunk/spambayes/spambayes/test/test_smtpproxy.py 2011-02-25 20:05:02 UTC (rev 3271) @@ -60,7 +60,6 @@ import sys import socket import getopt -import asyncore import operator import unittest import thread @@ -69,6 +68,7 @@ import sb_test_support sb_test_support.fix_sys_path() +from spambayes import asyncore from spambayes import Dibbler from spambayes import tokenizer from spambayes.Options import options Added: trunk/spambayes/tox.ini =================================================================== --- trunk/spambayes/tox.ini (rev 0) +++ trunk/spambayes/tox.ini 2011-02-25 20:05:02 UTC (rev 3271) @@ -0,0 +1,11 @@ +# content of: tox.ini , put in same dir as setup.py +[tox] +envlist = py24,py25,py26,py27 + +[testenv] +deps= + nose + ZODB3 + lockfile + pydns +commands=nosetests Property changes on: trunk/spambayes/tox.ini ___________________________________________________________________ Added: svn:eol-style + native This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.