home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pytho152.zip / emx / lib / python1.5 / asynchat.py < prev    next >
Text File  |  2000-08-10  |  9KB  |  291 lines

  1. # -*- Mode: Python; tab-width: 4 -*-
  2. #    $Id: asynchat.py,v 1.1 1999/01/12 20:19:24 guido Exp $
  3. #    Author: Sam Rushing <rushing@nightmare.com>
  4.  
  5. # ======================================================================
  6. # Copyright 1996 by Sam Rushing
  7. #                         All Rights Reserved
  8. # Permission to use, copy, modify, and distribute this software and
  9. # its documentation for any purpose and without fee is hereby
  10. # granted, provided that the above copyright notice appear in all
  11. # copies and that both that copyright notice and this permission
  12. # notice appear in supporting documentation, and that the name of Sam
  13. # Rushing not be used in advertising or publicity pertaining to
  14. # distribution of the software without specific, written prior
  15. # permission.
  16. # SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
  17. # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
  18. # NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
  19. # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
  20. # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  21. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  22. # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  23. # ======================================================================
  24.  
  25. import socket
  26. import asyncore
  27. import string
  28.  
  29. # This class adds support for 'chat' style protocols - where one side
  30. # sends a 'command', and the other sends a response (examples would be
  31. # the common internet protocols - smtp, nntp, ftp, etc..).
  32.  
  33. # The handle_read() method looks at the input stream for the current
  34. # 'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
  35. # for multi-line output), calling self.found_terminator() on its
  36. # receipt.
  37.  
  38. # for example:
  39. # Say you build an async nntp client using this class.  At the start
  40. # of the connection, you'll have self.terminator set to '\r\n', in
  41. # order to process the single-line greeting.  Just before issuing a
  42. # 'LIST' command you'll set it to '\r\n.\r\n'.  The output of the LIST
  43. # command will be accumulated (using your own 'collect_incoming_data'
  44. # method) up to the terminator, and then control will be returned to
  45. # you - by calling your self.found_terminator() method
  46.  
  47. class async_chat (asyncore.dispatcher):
  48.     """This is an abstract class.  You must derive from this class, and add
  49.     the two methods collect_incoming_data() and found_terminator()"""
  50.  
  51.     # these are overridable defaults
  52.  
  53.     ac_in_buffer_size    = 4096
  54.     ac_out_buffer_size    = 4096
  55.  
  56.     def __init__ (self, conn=None):
  57.         self.ac_in_buffer = ''
  58.         self.ac_out_buffer = ''
  59.         self.producer_fifo = fifo()
  60.         asyncore.dispatcher.__init__ (self, conn)
  61.  
  62.     def set_terminator (self, term):
  63.         "Set the input delimiter.  Can be a fixed string of any length, or None"
  64.         if term is None:
  65.             self.terminator = ''
  66.         else:
  67.             self.terminator = term
  68.  
  69.     def get_terminator (self):
  70.         return self.terminator
  71.  
  72.     # grab some more data from the socket,
  73.     # throw it to the collector method,
  74.     # check for the terminator,
  75.     # if found, transition to the next state.
  76.  
  77.     def handle_read (self):
  78.  
  79.         try:
  80.             data = self.recv (self.ac_in_buffer_size)
  81.         except socket.error, why:
  82.             import sys
  83.             self.handle_error (sys.exc_type, sys.exc_value, sys.exc_traceback)
  84.             return
  85.  
  86.         self.ac_in_buffer = self.ac_in_buffer + data
  87.  
  88.         # Continue to search for self.terminator in self.ac_in_buffer,
  89.         # while calling self.collect_incoming_data.  The while loop
  90.         # is necessary because we might read several data+terminator
  91.         # combos with a single recv(1024).
  92.  
  93.         while self.ac_in_buffer:
  94.             terminator = self.get_terminator()
  95.             terminator_len = len(terminator)
  96.             # 4 cases:
  97.             # 1) end of buffer matches terminator exactly:
  98.             #    collect data, transition
  99.             # 2) end of buffer matches some prefix:
  100.             #    collect data to the prefix
  101.             # 3) end of buffer does not match any prefix:
  102.             #    collect data
  103.             # 4) no terminator, just collect the data
  104.             if terminator:
  105.                 index = string.find (self.ac_in_buffer, terminator)
  106.                 if index != -1:
  107.                     # we found the terminator
  108.                     self.collect_incoming_data (self.ac_in_buffer[:index])
  109.                     self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
  110.                     # This does the Right Thing if the terminator is changed here.
  111.                     self.found_terminator()
  112.                 else:
  113.                     # check for a prefix of the terminator
  114.                     index = find_prefix_at_end (self.ac_in_buffer, terminator)
  115.                     if index:
  116.                         # we found a prefix, collect up to the prefix
  117.                         self.collect_incoming_data (self.ac_in_buffer[:-index])
  118.                         self.ac_in_buffer = self.ac_in_buffer[-index:]
  119.                         break
  120.                     else:
  121.                         # no prefix, collect it all
  122.                         self.collect_incoming_data (self.ac_in_buffer)
  123.                         self.ac_in_buffer = ''
  124.             else:
  125.                 # no terminator, collect it all
  126.                 self.collect_incoming_data (self.ac_in_buffer)
  127.                 self.ac_in_buffer = ''
  128.  
  129.     def handle_write (self):
  130.         self.initiate_send ()
  131.         
  132.     def handle_close (self):
  133.         self.close()
  134.  
  135.     def push (self, data):
  136.         self.producer_fifo.push (simple_producer (data))
  137.         self.initiate_send()
  138.  
  139.     def push_with_producer (self, producer):
  140.         self.producer_fifo.push (producer)
  141.         self.initiate_send()
  142.  
  143.     def readable (self):
  144.         return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
  145.  
  146.     def writable (self):
  147.         return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
  148.  
  149.     def close_when_done (self):
  150.         self.producer_fifo.push (None)
  151.  
  152.     # refill the outgoing buffer by calling the more() method
  153.     # of the first producer in the queue
  154.     def refill_buffer (self):
  155.         while 1:
  156.             if len(self.producer_fifo):
  157.                 p = self.producer_fifo.first()
  158.                 # a 'None' in the producer fifo is a sentinel,
  159.                 # telling us to close the channel.
  160.                 if p is None:
  161.                     if not self.ac_out_buffer:
  162.                         self.producer_fifo.pop()
  163.                         self.close()
  164.                     return
  165.                 data = p.more()
  166.                 if data:
  167.                     self.ac_out_buffer = self.ac_out_buffer + data
  168.                     return
  169.                 else:
  170.                     self.producer_fifo.pop()
  171.             else:
  172.                 return
  173.  
  174.     def initiate_send (self):
  175.         obs = self.ac_out_buffer_size
  176.         # try to refill the buffer
  177.         if (not self._push_mode) and (len (self.ac_out_buffer) < obs):
  178.             self.refill_buffer()
  179.  
  180.         if self.ac_out_buffer and self.connected:
  181.             # try to send the buffer
  182.             num_sent = self.send (self.ac_out_buffer[:obs])
  183.             if num_sent:
  184.                 self.ac_out_buffer = self.ac_out_buffer[num_sent:]
  185.  
  186.     def discard_buffers (self):
  187.         # Emergencies only!
  188.         self.ac_in_buffer = ''
  189.         self.ac_out_buffer == ''
  190.         while self.producer_fifo:
  191.             self.producer_fifo.pop()
  192.  
  193.     # ==================================================
  194.     # support for push mode.
  195.     # ==================================================
  196.     _push_mode = 0
  197.     def push_mode (self, boolean):
  198.         self._push_mode = boolean
  199.  
  200.     def writable_push (self):
  201.         return self.connected and len(self.ac_out_buffer)
  202.  
  203. class simple_producer:
  204.     def __init__ (self, data, buffer_size=512):
  205.         self.data = data
  206.         self.buffer_size = buffer_size
  207.  
  208.     def more (self):
  209.         if len (self.data) > self.buffer_size:
  210.             result = self.data[:self.buffer_size]
  211.             self.data = self.data[self.buffer_size:]
  212.             return result
  213.         else:
  214.             result = self.data
  215.             self.data = ''
  216.             return result
  217.  
  218. class fifo:
  219.     def __init__ (self, list=None):
  220.         if not list:
  221.             self.list = []
  222.         else:
  223.             self.list = list
  224.         
  225.     def __len__ (self):
  226.         return len(self.list)
  227.  
  228.     def first (self):
  229.         return self.list[0]
  230.  
  231.     def push (self, data):
  232.         self.list.append (data)
  233.  
  234.     def pop (self):
  235.         if self.list:
  236.             result = self.list[0]
  237.             del self.list[0]
  238.             return (1, result)
  239.         else:
  240.             return (0, None)
  241.  
  242. # Given 'haystack', see if any prefix of 'needle' is at its end.  This
  243. # assumes an exact match has already been checked.  Return the number of
  244. # characters matched.
  245. # for example:
  246. # f_p_a_e ("qwerty\r", "\r\n") => 1
  247. # f_p_a_e ("qwerty\r\n", "\r\n") => 2
  248. # f_p_a_e ("qwertydkjf", "\r\n") => 0
  249.  
  250. # this could maybe be made faster with a computed regex?
  251.  
  252. ##def find_prefix_at_end (haystack, needle):
  253. ##    nl = len(needle)
  254. ##    result = 0
  255. ##    for i in range (1,nl):
  256. ##        if haystack[-(nl-i):] == needle[:(nl-i)]:
  257. ##            result = nl-i
  258. ##            break
  259. ##    return result
  260.  
  261. # yes, this is about twice as fast, but still seems
  262. # to be neglible CPU.  The previous could do about 290
  263. # searches/sec. the new one about 555/sec.
  264.  
  265. import regex
  266.  
  267. prefix_cache = {}
  268.  
  269. def prefix_regex (needle):
  270.     if prefix_cache.has_key (needle):
  271.         return prefix_cache[needle]
  272.     else:
  273.         reg = needle[-1]
  274.         for i in range(1,len(needle)):
  275.             reg = '%c\(%s\)?' % (needle[-(i+1)], reg)
  276.         reg = regex.compile (reg+'$')
  277.         prefix_cache[needle] = reg, len(needle)
  278.         return reg, len(needle)
  279.  
  280. def find_prefix_at_end (haystack, needle):
  281.     reg, length = prefix_regex (needle)
  282.     lh = len(haystack)
  283.     result = reg.search (haystack, max(0,lh-length))
  284.     if result >= 0:
  285.         return (lh - result)
  286.     else:
  287.         return 0
  288.