Changeset 3175

Show
Ignore:
Timestamp:
05/28/07 18:47:32 (17 years ago)
Author:
jerome
Message:

Rewrote PJL accounter using threads and non-blocking I/O.
Move shared constants to a dedicated module.

Location:
pykota/trunk/pykota
Files:
2 modified
1 copied

Legend:

Unmodified
Added
Removed
  • pykota/trunk/pykota/accounters/pjl.py

    r3163 r3175  
    2626import os 
    2727import socket 
     28import errno 
    2829import time 
    29 import signal 
    30  
    31 # NB : in fact these variables don't do much, since the time  
    32 # is in fact wasted in the sock.recv() blocking call, with the timeout 
    33 ITERATIONDELAY = 1   # 1 Second 
    34 STABILIZATIONDELAY = 3 # We must read three times the same value to consider it to be stable 
    35 NOPRINTINGMAXDELAY = 60 # The printer must begin to print within 60 seconds byb default. 
    36  
    37 # Here's the real thing : 
    38 TIMEOUT = 5 
     30import threading 
     31import Queue 
     32 
     33from pykota import constants 
     34 
     35FORMFEEDCHAR = 0x0c     # Form Feed character, ends PJL answers. 
    3936 
    4037# Old method : pjlMessage = "\033%-12345X@PJL USTATUSOFF\r\n@PJL INFO STATUS\r\n@PJL INFO PAGECOUNT\r\n\033%-12345X" 
     
    6764            self.port = 9100 
    6865        self.printerInternalPageCounter = self.printerStatus = None 
    69         self.timedout = 0 
    70          
    71     def alarmHandler(self, signum, frame) :     
    72         """Query has timedout, handle this.""" 
    73         self.timedout = 1 
    74         raise IOError, "Waiting for PJL answer timed out. Please try again later." 
    75          
    76     def retrievePJLValues(self) :     
    77         """Retrieves a printer's internal page counter and status via PJL.""" 
     66        self.closed = False 
     67        self.sock = None 
     68        self.queue = None 
     69        self.readthread = None 
     70        self.quitEvent = threading.Event() 
     71         
     72    def __del__(self) :     
     73        """Ensures the network connection is closed at object deletion time.""" 
     74        self.close() 
     75         
     76    def open(self) :     
     77        """Opens the network connection.""" 
    7878        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    7979        try : 
     
    8181        except socket.error, msg : 
    8282            self.parent.filter.printInfo(_("Problem during connection to %s:%s : %s") % (self.printerHostname, self.port, str(msg)), "warn") 
     83            return False 
    8384        else : 
    84             self.parent.filter.logdebug("Connected to printer %s" % self.printerHostname) 
     85            sock.setblocking(False) 
     86            self.sock = sock 
     87            self.closed = False 
     88            self.quitEvent.clear() 
     89            self.queue = Queue.Queue(0) 
     90            self.readthread = threading.Thread(target=self.readloop) 
     91            self.readthread.start() 
     92            self.parent.filter.logdebug("Connected to printer %s:%s" % (self.printerHostname, self.port)) 
     93            return True 
     94         
     95    def close(self) :     
     96        """Closes the network connection.""" 
     97        if not self.closed : 
     98            self.quitEvent.set() 
     99            if self.readthread is not None : 
     100                self.readthread.join() 
     101                self.readthread = None 
     102            if self.sock is not None : 
     103                self.sock.close() 
     104                self.sock = None 
     105            self.parent.filter.logdebug("Connection to %s:%s is now closed." % (self.printerHostname, self.port)) 
     106            self.queue = None 
     107            self.closed = True 
     108             
     109    def readloop(self) :         
     110        """Reading loop thread.""" 
     111        self.parent.filter.logdebug("Reading thread started.") 
     112        buffer = [] 
     113        while not self.quitEvent.isSet() : 
    85114            try : 
    86                 sock.send(pjlMessage) 
    87             except socket.error, msg : 
    88                 self.parent.filter.printInfo(_("Problem while sending PJL query to %s:%s : %s") % (self.printerHostname, self.port, str(msg)), "warn") 
     115                answer = self.sock.recv(4096) 
     116            except socket.error, (err, msg) : 
     117                time.sleep(0.1) # We will try again later in all cases 
     118                if err != errno.EAGAIN : 
     119                    self.parent.filter.printInfo(_("Problem while receiving PJL answer from %s:%s : %s") % (self.printerHostname, self.port, str(msg)), "warn") 
    89120            else :     
    90                 self.parent.filter.logdebug("Query sent to %s : %s" % (self.printerHostname, repr(pjlMessage))) 
    91                 actualpagecount = self.printerStatus = None 
    92                 self.timedout = 0 
    93                 while (self.timedout == 0) or (actualpagecount is None) or (self.printerStatus is None) : 
    94                     signal.signal(signal.SIGALRM, self.alarmHandler) 
    95                     signal.alarm(TIMEOUT) 
    96                     try : 
    97                         answer = sock.recv(1024) 
    98                     except IOError, msg :     
    99                         self.parent.filter.logdebug("I/O Error [%s] : alarm handler probably called" % msg) 
    100                         break   # our alarm handler was launched, probably 
    101                     except socket.error, msg : 
    102                         self.parent.filter.printInfo(_("Problem while receiving PJL answer from %s:%s : %s") % (self.printerHostname, self.port, str(msg)), "warn") 
    103                     else :     
    104                         readnext = 0 
    105                         self.parent.filter.logdebug("PJL answer : %s" % repr(answer)) 
    106                         for line in [l.strip() for l in answer.split()] :  
    107                             if line.startswith("CODE=") : 
    108                                 self.printerStatus = line.split("=")[1] 
    109                                 self.parent.filter.logdebug("Found status : %s" % self.printerStatus) 
    110                             elif line.startswith("PAGECOUNT=") :     
    111                                 try : 
    112                                     actualpagecount = int(line.split('=')[1].strip()) 
    113                                 except ValueError :     
    114                                     self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip()) 
    115                                 else : 
    116                                     self.parent.filter.logdebug("Found pages counter : %s" % actualpagecount) 
    117                             elif line.startswith("PAGECOUNT") :     
    118                                 readnext = 1 # page counter is on next line 
    119                             elif readnext :     
    120                                 try : 
    121                                     actualpagecount = int(line.strip()) 
    122                                 except ValueError :     
    123                                     self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip()) 
    124                                 else : 
    125                                     self.parent.filter.logdebug("Found pages counter : %s" % actualpagecount) 
    126                                     readnext = 0 
    127                     signal.alarm(0) 
    128                 self.printerInternalPageCounter = max(actualpagecount, self.printerInternalPageCounter) 
    129         sock.close() 
    130         self.parent.filter.logdebug("Connection to %s is now closed." % self.printerHostname) 
     121                buffer.append(answer) 
     122                if answer.endswith(constants.FORMFEEDCHAR) : 
     123                    self.queue.put("".join(buffer)) 
     124                    buffer = [] 
     125        if buffer :              
     126            self.queue.put("".join(buffer))             
     127        self.parent.filter.logdebug("Reading thread ended.") 
     128             
     129    def retrievePJLValues(self) :     
     130        """Retrieves a printer's internal page counter and status via PJL.""" 
     131        try : 
     132            self.sock.send(pjlMessage) 
     133        except socket.error, msg : 
     134            self.parent.filter.printInfo(_("Problem while sending PJL query to %s:%s : %s") % (self.printerHostname, self.port, str(msg)), "warn") 
     135        else :     
     136            self.parent.filter.logdebug("Query sent to %s : %s" % (self.printerHostname, repr(pjlMessage))) 
     137            actualpagecount = self.printerStatus = None 
     138            while (actualpagecount is None) or (self.printerStatus is None) : 
     139                try : 
     140                    answer = self.queue.get(True, 5) 
     141                except Queue.Empty :     
     142                    self.parent.filter.logdebug("Timeout when reading printer's answer from %s:%s" % (self.printerHostname, self.port)) 
     143                else :     
     144                    readnext = False 
     145                    self.parent.filter.logdebug("PJL answer : %s" % repr(answer)) 
     146                    for line in [l.strip() for l in answer.split()] :  
     147                        if line.startswith("CODE=") : 
     148                            self.printerStatus = line.split("=")[1] 
     149                            self.parent.filter.logdebug("Found status : %s" % self.printerStatus) 
     150                        elif line.startswith("PAGECOUNT=") :     
     151                            try : 
     152                                actualpagecount = int(line.split('=')[1].strip()) 
     153                            except ValueError :     
     154                                self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip()) 
     155                            else : 
     156                                self.parent.filter.logdebug("Found pages counter : %s" % actualpagecount) 
     157                        elif line.startswith("PAGECOUNT") :     
     158                            readnext = True # page counter is on next line 
     159                        elif readnext :     
     160                            try : 
     161                                actualpagecount = int(line.strip()) 
     162                            except ValueError :     
     163                                self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip()) 
     164                            else : 
     165                                self.parent.filter.logdebug("Found pages counter : %s" % actualpagecount) 
     166                                readnext = False 
     167            self.printerInternalPageCounter = max(actualpagecount, self.printerInternalPageCounter) 
    131168         
    132169    def waitPrinting(self) : 
     
    135172            noprintingmaxdelay = int(self.parent.filter.config.getNoPrintingMaxDelay(self.parent.filter.PrinterName)) 
    136173        except (TypeError, AttributeError) : # NB : AttributeError in testing mode because I'm lazy ! 
    137             noprintingmaxdelay = NOPRINTINGMAXDELAY 
     174            noprintingmaxdelay = constants.NOPRINTINGMAXDELAY 
    138175            self.parent.filter.logdebug("No max delay defined for printer %s, using %i seconds." % (self.parent.filter.PrinterName, noprintingmaxdelay)) 
    139176        if not noprintingmaxdelay : 
     
    144181        timebefore = time.time() 
    145182        firstvalue = None 
    146         while 1: 
     183        while True : 
    147184            self.retrievePJLValues() 
    148185            if self.printerStatus in ('10023', '10003') : 
     
    175212                            break 
    176213            self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.PrinterName) 
    177             time.sleep(ITERATIONDELAY) 
     214            time.sleep(constants.ITERATIONDELAY) 
    178215         
    179216    def waitIdle(self) : 
    180217        """Waits for printer status being 'idle'.""" 
    181218        idle_num = 0 
    182         while 1 : 
     219        while True : 
    183220            self.retrievePJLValues() 
    184221            if self.printerStatus in ('10000', '10001', '35078', '40000') : 
     
    189226                    return  
    190227                idle_num += 1 
    191                 if idle_num >= STABILIZATIONDELAY : 
     228                if idle_num >= constants.STABILIZATIONDELAY : 
    192229                    # printer status is stable, we can exit 
    193230                    break 
     
    195232                idle_num = 0 
    196233            self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.PrinterName) 
    197             time.sleep(ITERATIONDELAY) 
     234            time.sleep(constants.ITERATIONDELAY) 
    198235     
    199236    def retrieveInternalPageCounter(self) : 
    200237        """Returns the page counter from the printer via internal PJL handling.""" 
    201         try : 
    202             if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \ 
    203                (os.environ.get("PYKOTAACTION") == "ALLOW") and \ 
    204                (os.environ.get("PYKOTAPHASE") == "AFTER") and \ 
    205                self.parent.filter.JobSizeBytes : 
    206                 self.waitPrinting() 
    207             self.waitIdle()     
    208         except :     
    209             self.parent.filter.printInfo(_("PJL querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (self.printerInternalPageCounter, self.parent.filter.PrinterName), "warn") 
    210             raise 
    211         return self.printerInternalPageCounter 
     238        while not self.open() : 
     239            self.parent.filter.logdebug("Will retry in 1 second.") 
     240            time.sleep(1) 
     241        try : 
     242            try : 
     243                if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \ 
     244                   (os.environ.get("PYKOTAACTION") == "ALLOW") and \ 
     245                   (os.environ.get("PYKOTAPHASE") == "AFTER") and \ 
     246                   self.parent.filter.JobSizeBytes : 
     247                    self.waitPrinting() 
     248                self.waitIdle()     
     249            except :     
     250                self.parent.filter.printInfo(_("PJL querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (self.printerInternalPageCounter, self.parent.filter.PrinterName), "warn") 
     251                raise 
     252            else :     
     253                return self.printerInternalPageCounter 
     254        finally :         
     255            self.close() 
    212256             
    213257def main(hostname) : 
  • pykota/trunk/pykota/accounters/snmp.py

    r3163 r3175  
    2929""" 
    3030 
    31 ITERATIONDELAY = 4      # time to sleep between two loops 
    32 STABILIZATIONDELAY = 5  # number of consecutive times the idle status must be seen before we consider it to be stable 
    33 NOPRINTINGMAXDELAY = 60 # The printer must begin to print within 60 seconds by default. 
    3431 
    3532import sys 
     
    5249else : 
    5350    hasV4 = True 
     51 
     52from pykota import constants 
    5453 
    5554#                       
     
    158157            noprintingmaxdelay = int(self.parent.filter.config.getNoPrintingMaxDelay(self.parent.filter.PrinterName)) 
    159158        except (TypeError, AttributeError) : # NB : AttributeError in testing mode because I'm lazy ! 
    160             noprintingmaxdelay = NOPRINTINGMAXDELAY 
     159            noprintingmaxdelay = constants.NOPRINTINGMAXDELAY 
    161160            self.parent.filter.logdebug("No max delay defined for printer %s, using %i seconds." % (self.parent.filter.PrinterName, noprintingmaxdelay)) 
    162161        if not noprintingmaxdelay : 
     
    204203                            break 
    205204            self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.PrinterName)     
    206             time.sleep(ITERATIONDELAY) 
     205            time.sleep(constants.ITERATIONDELAY) 
    207206         
    208207    def waitIdle(self) : 
     
    226225                    return  
    227226                idle_num += 1 
    228                 if idle_num >= STABILIZATIONDELAY : 
     227                if idle_num >= constants.STABILIZATIONDELAY : 
    229228                    # printer status is stable, we can exit 
    230229                    break 
     
    232231                idle_num = 0 
    233232            self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.PrinterName)     
    234             time.sleep(ITERATIONDELAY) 
     233            time.sleep(constants.ITERATIONDELAY) 
    235234             
    236235    def retrieveInternalPageCounter(self) : 
  • pykota/trunk/pykota/constants.py

    r3133 r3175  
    22# -*- coding: ISO-8859-15 -*- 
    33# 
    4 # PyKota : Print Quotas for CUPS and LPRng 
     4# PyKota : Print Quotas for CUPS 
    55# 
    66# (c) 2003, 2004, 2005, 2006, 2007 Jerome Alet <alet@librelogiciel.com> 
     
    2121# $Id$ 
    2222# 
     23 
     24"""This module contains the definitions of constants used by PyKota.""" 
     25 
     26ITERATIONDELAY = 4      # time to sleep between two loops 
     27STABILIZATIONDELAY = 5  # number of consecutive times the idle status must be seen before we consider it to be stable 
     28NOPRINTINGMAXDELAY = 60 # The printer must begin to print within 60 seconds by default.