Changeset 2635

Show
Ignore:
Timestamp:
01/31/06 12:32:34 (18 years ago)
Author:
jerome
Message:

Introduced the 'preaccounter' directive.

Location:
pykota/trunk
Files:
7 modified

Legend:

Unmodified
Added
Removed
  • pykota/trunk/bin/cupspykota

    r2632 r2635  
    380380        """Computes the job size with a software method.""" 
    381381        self.logdebug("Precomputing job's size...") 
    382         jobsize = 0 
    383         if self.JobSizeBytes : 
    384             try : 
    385                 from pkpgpdls import analyzer, pdlparser 
    386             except ImportError :     
    387                 self.printInfo("pkpgcounter is now distributed separately, please grab it from http://www.librelogiciel.com/software/pkpgcounter/action_Download", "error") 
    388                 self.printInfo("Precomputed job size will be forced to 0 pages.", "error") 
    389             else :      
    390                 infile = open(self.DataFile, "rb") 
    391                 try : 
    392                     parser = analyzer.PDLAnalyzer(infile) 
    393                     jobsize = parser.getJobSize() 
    394                 except pdlparser.PDLParserError, msg :     
    395                     # Here we just log the failure, but 
    396                     # we finally ignore it and return 0 since this 
    397                     # computation is just an indication of what the 
    398                     # job's size MAY be. 
    399                     self.printInfo(_("Unable to precompute the job's size with the generic PDL analyzer : %s") % msg, "warn") 
    400                 else :     
    401                     if self.InputFile is not None : 
    402                         # when a filename is passed as an argument, the backend  
    403                         # must generate the correct number of copies. 
    404                         jobsize *= self.Copies 
    405                 infile.close()         
    406         self.softwareJobSize = jobsize 
     382        self.preaccounter.beginJob(None) 
     383        self.preaccounter.endJob(None) 
     384        self.softwareJobSize = self.preaccounter.getJobSize(None) 
    407385        self.logdebug("Precomputed job's size is %s pages." % self.softwareJobSize) 
    408386         
     
    11781156            wrapper.initBackendParameters() 
    11791157            wrapper.saveDatasAndCheckSum() 
     1158            wrapper.preaccounter = openAccounter(wrapper, ispreaccounter=1) 
    11801159            wrapper.accounter = openAccounter(wrapper) 
    11811160            wrapper.precomputeJobSize() 
  • pykota/trunk/conf/pykota.conf.sample

    r2631 r2635  
    563563# and uses pkpgcounter's code internally. 
    564564accounter: software() 
     565 
     566# What is the "pre"-accounter used for precomputing the job's size. 
     567# 
     568# Supported values are : 
     569# 
     570#  preaccounter: software()        
     571#  preaccounter: software(/path/to/your/script) 
     572# 
     573# NB : the preaccounter directive doesn't support hardware() for obvious reasons. 
     574# If unset, "software()" is assumed. If you use your own script, ensure that it 
     575# only prints the job's number of pages (or an estimation of it) on its standard output. 
     576# 
     577# This value can be set either globally or on a per printer basis 
     578# If both are defined, the printer option has priority. 
     579# 
     580preaccounter: software() 
     581 
    565582 
    566583# What should we do if the accounter's subprocess doesn't return 
  • pykota/trunk/NEWS

    r2631 r2635  
    2424    - 1.24alpha8 : 
    2525     
     26        - The 'preaccounter' configuration directive was introduced to control 
     27          which parser to use for precomputation of the job's size. When unset, 
     28          the internal parser is used just like it was previously. See 
     29          conf/pykota.conf.sample for more details. 
     30           
    2631        - Now subprocesses launched through the overwrite_jobticket directive 
    2732          can also output 'CANCEL', to represent the user's own choice to 
  • pykota/trunk/pykota/accounter.py

    r2622 r2635  
    3636class AccounterBase :     
    3737    """A class to account print usage by querying printers.""" 
    38     def __init__(self, kotafilter, arguments) : 
     38    def __init__(self, kotafilter, arguments, ispreaccounter=0) : 
    3939        """Sets instance vars depending on the current printer.""" 
    4040        self.filter = kotafilter 
     
    4242        self.onerror = self.filter.config.getPrinterOnAccounterError(self.filter.PrinterName) 
    4343        self.isSoftware = 1 # by default software accounting 
     44        self.isPreAccounter = ispreaccounter  
    4445         
    4546    def getLastPageCounter(self) :     
     
    5859         
    5960        # get last job information for this printer 
    60         if not printer.LastJob.Exists : 
    61             # The printer hasn't been used yet, from PyKota's point of view 
    62             self.LastPageCounter = 0 
    63         else :     
    64             # get last job size and page counter from Quota Storage 
    65             # Last lifetime page counter before actual job is  
    66             # last page counter + last job size 
    67             self.LastPageCounter = int(printer.LastJob.PrinterPageCounter or 0) + int(printer.LastJob.JobSize or 0) 
     61        if not self.isPreAccounter : 
     62            # TODO : check if this code is still needed 
     63            if not printer.LastJob.Exists : 
     64                # The printer hasn't been used yet, from PyKota's point of view 
     65                self.LastPageCounter = 0 
     66            else :     
     67                # get last job size and page counter from Quota Storage 
     68                # Last lifetime page counter before actual job is  
     69                # last page counter + last job size 
     70                self.LastPageCounter = int(printer.LastJob.PrinterPageCounter or 0) + int(printer.LastJob.JobSize or 0) 
    6871         
    6972    def fakeBeginJob(self) :     
     
    8689        raise RuntimeError, "AccounterBase.computeJobSize() must be overriden !" 
    8790         
    88 def openAccounter(kotafilter) : 
     91def openAccounter(kotafilter, ispreaccounter=0) : 
    8992    """Returns a connection handle to the appropriate accounter.""" 
    90     (backend, args) = kotafilter.config.getAccounterBackend(kotafilter.PrinterName) 
     93    if ispreaccounter : 
     94        (backend, args) = kotafilter.config.getPreAccounterBackend(kotafilter.PrinterName) 
     95    else : 
     96        (backend, args) = kotafilter.config.getAccounterBackend(kotafilter.PrinterName) 
    9197    try : 
    9298        exec "from pykota.accounters import %s as accounterbackend" % backend.lower() 
     
    94100        raise PyKotaAccounterError, _("Unsupported accounter backend %s") % backend 
    95101    else :     
    96         return accounterbackend.Accounter(kotafilter, args) 
     102        return accounterbackend.Accounter(kotafilter, args, ispreaccounter) 
  • pykota/trunk/pykota/accounters/hardware.py

    r2622 r2635  
    3131 
    3232class Accounter(AccounterBase) : 
    33     def __init__(self, kotabackend, arguments) : 
     33    def __init__(self, kotabackend, arguments, ispreaccounter=0) : 
    3434        """Initializes querying accounter.""" 
    3535        AccounterBase.__init__(self, kotabackend, arguments) 
  • pykota/trunk/pykota/accounters/software.py

    r2622 r2635  
    3030    def computeJobSize(self) :     
    3131        """Feeds an external command with our datas to let it compute the job size, and return its value.""" 
     32        if (not self.isPreAccounter) and \ 
     33            (self.filter.accounter.arguments == self.filter.preaccounter.arguments) : 
     34            # if precomputing has been done and both accounter and preaccounter are 
     35            # configured the same, no need to launch a second pass since we already 
     36            # know the result. 
     37            self.filter.logdebug("Precomputing pass told us that job is %s pages long." % self.filter.softwareJobSize) 
     38            return self.filter.softwareJobSize   # Optimize : already computed ! 
     39             
     40        if self.arguments : 
     41            self.filter.logdebug("Using external script %s to compute job's size." % self.arguments) 
     42            return self.withExternalScript() 
     43        else :     
     44            self.filter.logdebug("Using internal parser to compute job's size.") 
     45            return self.withInternalParser() 
     46         
     47    def withInternalParser(self) :     
     48        """Does software accounting through an external script.""" 
     49        jobsize = 0 
     50        if self.filter.JobSizeBytes : 
     51            try : 
     52                from pkpgpdls import analyzer, pdlparser 
     53            except ImportError :     
     54                self.filter.printInfo("pkpgcounter is now distributed separately, please grab it from http://www.librelogiciel.com/software/pkpgcounter/action_Download", "error") 
     55                self.filter.printInfo("Precomputed job size will be forced to 0 pages.", "error") 
     56            else :      
     57                infile = open(self.filter.DataFile, "rb") 
     58                try : 
     59                    parser = analyzer.PDLAnalyzer(infile) 
     60                    jobsize = parser.getJobSize() 
     61                except pdlparser.PDLParserError, msg :     
     62                    # Here we just log the failure, but 
     63                    # we finally ignore it and return 0 since this 
     64                    # computation is just an indication of what the 
     65                    # job's size MAY be. 
     66                    self.filter.printInfo(_("Unable to precompute the job's size with the generic PDL analyzer : %s") % msg, "warn") 
     67                else :     
     68                    if self.filter.InputFile is not None : 
     69                        # when a filename is passed as an argument, the backend  
     70                        # must generate the correct number of copies. 
     71                        jobsize *= self.filter.Copies 
     72                infile.close()         
     73        return jobsize         
     74                 
     75    def withExternalScript(self) :     
     76        """Does software accounting through an external script.""" 
    3277        self.filter.printInfo(_("Launching SOFTWARE(%s)...") % self.arguments) 
    33         if not self.arguments : 
    34             pagecounter = self.filter.softwareJobSize   # Optimize : already computed ! 
    35             self.filter.logdebug("Internal software accounter said job is %s pages long." % repr(pagecounter)) 
    36         else : 
    37             MEGABYTE = 1024*1024 
    38             infile = open(self.filter.DataFile, "rb") 
    39             child = popen2.Popen4(self.arguments) 
    40             try : 
    41                 data = infile.read(MEGABYTE)     
    42                 while data : 
    43                     child.tochild.write(data) 
    44                     data = infile.read(MEGABYTE) 
    45                 child.tochild.flush() 
    46                 child.tochild.close()     
    47             except (IOError, OSError), msg :     
    48                 msg = "%s : %s" % (self.arguments, msg)  
    49                 self.filter.printInfo(_("Unable to compute job size with accounter %s") % msg) 
    50             infile.close() 
    51             pagecounter = None 
    52             try : 
    53                 answer = child.fromchild.read() 
    54             except (IOError, OSError), msg :     
    55                 msg = "%s : %s" % (self.arguments, msg)  
    56                 self.filter.printInfo(_("Unable to compute job size with accounter %s") % msg) 
    57             else :     
    58                 lines = [l.strip() for l in answer.split("\n")] 
    59                 for i in range(len(lines)) :  
    60                     try : 
    61                         pagecounter = int(lines[i]) 
    62                     except (AttributeError, ValueError) : 
    63                         self.filter.printInfo(_("Line [%s] skipped in accounter's output. Trying again...") % lines[i]) 
    64                     else :     
    65                         break 
    66             child.fromchild.close() 
     78        MEGABYTE = 1024*1024 
     79        infile = open(self.filter.DataFile, "rb") 
     80        child = popen2.Popen4(self.arguments) 
     81        try : 
     82            data = infile.read(MEGABYTE)     
     83            while data : 
     84                child.tochild.write(data) 
     85                data = infile.read(MEGABYTE) 
     86            child.tochild.flush() 
     87            child.tochild.close()     
     88        except (IOError, OSError), msg :     
     89            msg = "%s : %s" % (self.arguments, msg)  
     90            self.filter.printInfo(_("Unable to compute job size with accounter %s") % msg) 
     91        infile.close() 
     92        pagecounter = None 
     93        try : 
     94            answer = child.fromchild.read() 
     95        except (IOError, OSError), msg :     
     96            msg = "%s : %s" % (self.arguments, msg)  
     97            self.filter.printInfo(_("Unable to compute job size with accounter %s") % msg) 
     98        else :     
     99            lines = [l.strip() for l in answer.split("\n")] 
     100            for i in range(len(lines)) :  
     101                try : 
     102                    pagecounter = int(lines[i]) 
     103                except (AttributeError, ValueError) : 
     104                    self.filter.printInfo(_("Line [%s] skipped in accounter's output. Trying again...") % lines[i]) 
     105                else :     
     106                    break 
     107        child.fromchild.close() 
     108         
     109        try : 
     110            status = child.wait() 
     111        except OSError, msg :     
     112            self.filter.printInfo(_("Problem while waiting for software accounter pid %s to exit : %s") % (child.pid, msg)) 
     113        else :     
     114            if os.WIFEXITED(status) : 
     115                status = os.WEXITSTATUS(status) 
     116            self.filter.printInfo(_("Software accounter %s exit code is %s") % (self.arguments, str(status))) 
    67117             
    68             try : 
    69                 status = child.wait() 
    70             except OSError, msg :     
    71                 self.filter.printInfo(_("Problem while waiting for software accounter pid %s to exit : %s") % (child.pid, msg)) 
    72             else :     
    73                 if os.WIFEXITED(status) : 
    74                     status = os.WEXITSTATUS(status) 
    75                 self.filter.printInfo(_("Software accounter %s exit code is %s") % (self.arguments, str(status))) 
    76                  
    77             if pagecounter is None :     
    78                 message = _("Unable to compute job size with accounter %s") % self.arguments 
    79                 if self.onerror == "CONTINUE" : 
    80                     self.filter.printInfo(message, "error") 
    81                 else : 
    82                     raise PyKotaAccounterError, message 
    83             self.filter.logdebug("Software accounter %s said job is %s pages long." % (self.arguments, repr(pagecounter))) 
     118        if pagecounter is None :     
     119            message = _("Unable to compute job size with accounter %s") % self.arguments 
     120            if self.onerror == "CONTINUE" : 
     121                self.filter.printInfo(message, "error") 
     122            else : 
     123                raise PyKotaAccounterError, message 
     124        self.filter.logdebug("Software accounter %s said job is %s pages long." % (self.arguments, repr(pagecounter))) 
    84125             
    85126        return pagecounter or 0 
  • pykota/trunk/pykota/config.py

    r2622 r2635  
    190190        return url.strip()            
    191191     
     192    def getPreAccounterBackend(self, printername) :     
     193        """Returns the preaccounter backend to use for a given printer.""" 
     194        validaccounters = [ "software" ]      
     195        try : 
     196            fullaccounter = self.getPrinterOption(printername, "preaccounter").strip() 
     197        except PyKotaConfigError :     
     198            return ("software", "") 
     199        else :     
     200            flower = fullaccounter.lower() 
     201            if flower.startswith("software") :     
     202                try : 
     203                    (accounter, args) = [x.strip() for x in fullaccounter.split('(', 1)] 
     204                except ValueError :     
     205                    raise PyKotaConfigError, _("Invalid preaccounter %s for printer %s") % (fullaccounter, printername) 
     206                if args.endswith(')') : 
     207                    args = args[:-1].strip() 
     208                return ("software", args) 
     209            else : 
     210                raise PyKotaConfigError, _("Option preaccounter in section %s only supports values in %s") % (printername, str(validaccounters)) 
     211         
    192212    def getAccounterBackend(self, printername) :     
    193         """Returns the accounter backend to use for a given printer. 
    194          
    195            if it is not set, it defaults to 'hardware' which means ask printer 
    196            for its internal lifetime page counter. 
    197         """    
     213        """Returns the accounter backend to use for a given printer.""" 
    198214        validaccounters = [ "hardware", "software" ]      
    199215        fullaccounter = self.getPrinterOption(printername, "accounter").strip()