Index: pykota/trunk/pykota/tool.py
===================================================================
--- pykota/trunk/pykota/tool.py (revision 2395)
+++ pykota/trunk/pykota/tool.py (revision 2409)
@@ -634,429 +634,2 @@
         return action        
         
-class PyKotaFilterOrBackend(PyKotaTool) :    
-    """Class for the PyKota filter or backend."""
-    def __init__(self) :
-        """Initialize local datas from current environment."""
-        # We begin with ignoring signals, we may de-ignore them later on.
-        self.gotSigTerm = 0
-        signal.signal(signal.SIGTERM, signal.SIG_IGN)
-        # signal.signal(signal.SIGCHLD, signal.SIG_IGN)
-        signal.signal(signal.SIGPIPE, signal.SIG_IGN)
-        
-        PyKotaTool.__init__(self)
-        (self.printingsystem, \
-         self.printerhostname, \
-         self.printername, \
-         self.username, \
-         self.jobid, \
-         self.inputfile, \
-         self.copies, \
-         self.title, \
-         self.options, \
-         self.originalbackend) = self.extractInfoFromCupsOrLprng()
-         
-    def deferredInit(self) :
-        """Deferred initialization."""
-        PyKotaTool.deferredInit(self)
-        
-        arguments = " ".join(['"%s"' % arg for arg in sys.argv])
-        self.logdebug(_("Printing system %s, args=%s") % (str(self.printingsystem), arguments))
-        
-        self.username = self.username or pwd.getpwuid(os.geteuid())[0] # use CUPS' user when printing test page from CUPS web interface, otherwise username is empty
-        
-        if self.printingsystem == "CUPS" :
-            self.extractDatasFromCups()
-        
-        (newusername, newbillingcode, newaction) = self.overwriteJobTicket()
-        if newusername :
-            self.printInfo(_("Job ticket overwritten : new username = [%s]") % newusername)
-            self.username = newusername
-        if newbillingcode :
-            self.printInfo(_("Job ticket overwritten : new billing code = [%s]") % newbillingcode)
-            self.overwrittenBillingCode = newbillingcode
-        else :    
-            self.overwrittenBillingCode = None
-        if newaction :    
-            self.printInfo(_("Job ticket overwritten : job will be denied (but a bit later)."))
-            self.mustDeny = 1
-        else :    
-            self.mustDeny = 0
-        
-        # do we want to strip out the Samba/Winbind domain name ?
-        separator = self.config.getWinbindSeparator()
-        if separator is not None :
-            self.username = self.username.split(separator)[-1]
-            
-        # do we want to lowercase usernames ?    
-        if self.config.getUserNameToLower() :
-            self.username = self.username.lower()
-            
-        # do we want to strip some prefix off of titles ?    
-        stripprefix = self.config.getStripTitle(self.printername)
-        if stripprefix :
-            if fnmatch.fnmatch(self.title[:len(stripprefix)], stripprefix) :
-                self.logdebug("Prefix [%s] removed from job's title [%s]." % (stripprefix, self.title))
-                self.title = self.title[len(stripprefix):]
-            
-        self.preserveinputfile = self.inputfile 
-        try :
-            self.accounter = accounter.openAccounter(self)
-        except (config.PyKotaConfigError, accounter.PyKotaAccounterError), msg :    
-            self.crashed(msg)
-            raise
-        self.exportJobInfo()
-        self.jobdatastream = self.openJobDataStream()
-        self.checksum = self.computeChecksum()
-        self.softwareJobSize = self.precomputeJobSize()
-        os.environ["PYKOTAPRECOMPUTEDJOBSIZE"] = str(self.softwareJobSize)
-        os.environ["PYKOTAJOBSIZEBYTES"] = str(self.jobSizeBytes)
-        self.logdebug("Job size is %s bytes on %s pages." % (self.jobSizeBytes, self.softwareJobSize))
-        self.logdebug("Capturing SIGTERM events.")
-        signal.signal(signal.SIGTERM, self.sigterm_handler)
-        
-    def overwriteJobTicket(self) :    
-        """Should we overwrite the job's ticket (username and billingcode) ?"""
-        jobticketcommand = self.config.getOverwriteJobTicket(self.printername)
-        if jobticketcommand is not None :
-            username = billingcode = action = None
-            self.logdebug("Launching subprocess [%s] to overwrite the job's ticket." % jobticketcommand)
-            inputfile = os.popen(jobticketcommand, "r")
-            for line in inputfile.xreadlines() :
-                line = line.strip()
-                if line == "DENY" :
-                    self.logdebug("Seen DENY command.")
-                    action = "DENY"
-                elif line.startswith("USERNAME=") :    
-                    username = line.split("=", 1)[1].strip()
-                    self.logdebug("Seen new username [%s]" % username)
-                    action = None
-                elif line.startswith("BILLINGCODE=") :    
-                    billingcode = line.split("=", 1)[1].strip()
-                    self.logdebug("Seen new billing code [%s]" % billingcode)
-                    action = None
-            inputfile.close()    
-            return (username, billingcode, action)
-        else :
-            return (None, None, None)
-        
-    def sendBackChannelData(self, message, level="info") :    
-        """Sends an informational message to CUPS via back channel stream (stderr)."""
-        sys.stderr.write("%s: PyKota (PID %s) : %s\n" % (level.upper(), os.getpid(), message.strip()))
-        sys.stderr.flush()
-        
-    def computeChecksum(self) :    
-        """Computes the MD5 checksum of the job's datas, to be able to detect and forbid duplicate jobs."""
-        self.logdebug("Computing MD5 checksum for job %s" % self.jobid)
-        MEGABYTE = 1024*1024
-        checksum = md5.new()
-        while 1 :
-            data = self.jobdatastream.read(MEGABYTE) 
-            if not data :
-                break
-            checksum.update(data)    
-        self.jobdatastream.seek(0)
-        digest = checksum.hexdigest()
-        self.logdebug("MD5 checksum for job %s is %s" % (self.jobid, digest))
-        os.environ["PYKOTAMD5SUM"] = digest
-        return digest
-        
-    def openJobDataStream(self) :    
-        """Opens the file which contains the job's datas."""
-        if self.preserveinputfile is None :
-            # Job comes from sys.stdin, but this is not
-            # seekable and complexifies our task, so create
-            # a temporary file and use it instead
-            self.logdebug("Duplicating data stream from stdin to temporary file")
-            dummy = 0
-            MEGABYTE = 1024*1024
-            self.jobSizeBytes = 0
-            infile = tempfile.TemporaryFile()
-            while 1 :
-                data = sys.stdin.read(MEGABYTE) 
-                if not data :
-                    break
-                self.jobSizeBytes += len(data)    
-                if not (dummy % 10) :
-                    self.logdebug("%s bytes read..." % self.jobSizeBytes)
-                dummy += 1    
-                infile.write(data)
-            self.logdebug("%s bytes read total." % self.jobSizeBytes)
-            infile.flush()    
-            infile.seek(0)
-            return infile
-        else :    
-            # real file, just open it
-            self.regainPriv()
-            self.logdebug("Opening data stream %s" % self.preserveinputfile)
-            self.jobSizeBytes = os.stat(self.preserveinputfile)[6]
-            infile = open(self.preserveinputfile, "rb")
-            self.dropPriv()
-            return infile
-        
-    def closeJobDataStream(self) :    
-        """Closes the file which contains the job's datas."""
-        self.logdebug("Closing data stream.")
-        try :
-            self.jobdatastream.close()
-        except :    
-            pass
-        
-    def precomputeJobSize(self) :    
-        """Computes the job size with a software method."""
-        self.logdebug("Precomputing job's size with generic PDL analyzer...")
-        self.jobdatastream.seek(0)
-        try :
-            parser = analyzer.PDLAnalyzer(self.jobdatastream)
-            jobsize = parser.getJobSize()
-        except pdlparser.PDLParserError, msg :    
-            # Here we just log the failure, but
-            # we finally ignore it and return 0 since this
-            # computation is just an indication of what the
-            # job's size MAY be.
-            self.printInfo(_("Unable to precompute the job's size with the generic PDL analyzer : %s") % msg, "warn")
-            return 0
-        else :    
-            if ((self.printingsystem == "CUPS") \
-                and (self.preserveinputfile is not None)) \
-                or (self.printingsystem != "CUPS") :
-                return jobsize * self.copies
-            else :        
-                return jobsize
-            
-    def sigterm_handler(self, signum, frame) :
-        """Sets an attribute whenever SIGTERM is received."""
-        self.gotSigTerm = 1
-        os.environ["PYKOTASTATUS"] = "CANCELLED"
-        self.printInfo(_("SIGTERM received, job %s cancelled.") % self.jobid)
-        
-    def exportJobInfo(self) :    
-        """Exports job information to the environment."""
-        os.environ["PYKOTAUSERNAME"] = str(self.username)
-        os.environ["PYKOTAPRINTERNAME"] = str(self.printername)
-        os.environ["PYKOTAJOBID"] = str(self.jobid)
-        os.environ["PYKOTATITLE"] = self.title or ""
-        os.environ["PYKOTAFILENAME"] = self.preserveinputfile or ""
-        os.environ["PYKOTACOPIES"] = str(self.copies)
-        os.environ["PYKOTAOPTIONS"] = self.options or ""
-        os.environ["PYKOTAPRINTERHOSTNAME"] = self.printerhostname or "localhost"
-    
-    def exportUserInfo(self, userpquota) :
-        """Exports user information to the environment."""
-        os.environ["PYKOTAOVERCHARGE"] = str(userpquota.User.OverCharge)
-        os.environ["PYKOTALIMITBY"] = str(userpquota.User.LimitBy)
-        os.environ["PYKOTABALANCE"] = str(userpquota.User.AccountBalance or 0.0)
-        os.environ["PYKOTALIFETIMEPAID"] = str(userpquota.User.LifeTimePaid or 0.0)
-        os.environ["PYKOTAPAGECOUNTER"] = str(userpquota.PageCounter or 0)
-        os.environ["PYKOTALIFEPAGECOUNTER"] = str(userpquota.LifePageCounter or 0)
-        os.environ["PYKOTASOFTLIMIT"] = str(userpquota.SoftLimit)
-        os.environ["PYKOTAHARDLIMIT"] = str(userpquota.HardLimit)
-        os.environ["PYKOTADATELIMIT"] = str(userpquota.DateLimit)
-        os.environ["PYKOTAWARNCOUNT"] = str(userpquota.WarnCount)
-        
-        # not really an user information, but anyway
-        # exports the list of printers groups the current
-        # printer is a member of
-        os.environ["PYKOTAPGROUPS"] = ",".join([p.Name for p in self.storage.getParentPrinters(userpquota.Printer)])
-        
-    def prehook(self, userpquota) :
-        """Allows plugging of an external hook before the job gets printed."""
-        prehook = self.config.getPreHook(userpquota.Printer.Name)
-        if prehook :
-            self.logdebug("Executing pre-hook [%s]" % prehook)
-            os.system(prehook)
-        
-    def posthook(self, userpquota) :
-        """Allows plugging of an external hook after the job gets printed and/or denied."""
-        posthook = self.config.getPostHook(userpquota.Printer.Name)
-        if posthook :
-            self.logdebug("Executing post-hook [%s]" % posthook)
-            os.system(posthook)
-            
-    def printInfo(self, message, level="info") :        
-        """Sends a message to standard error."""
-        self.logger.log_message("%s" % message, level)
-        
-    def printMoreInfo(self, user, printer, message, level="info") :            
-        """Prefixes the information printed with 'user@printer(jobid) =>'."""
-        self.printInfo("%s@%s(%s) => %s" % (getattr(user, "Name", None), getattr(printer, "Name", None), self.jobid, message), level)
-        
-    def extractInfoFromCupsOrLprng(self) :    
-        """Returns a tuple (printingsystem, printerhostname, printername, username, jobid, filename, title, options, backend).
-        
-           Returns (None, None, None, None, None, None, None, None, None, None) if no printing system is recognized.
-        """
-        # Try to detect CUPS
-        if os.environ.has_key("CUPS_SERVERROOT") and os.path.isdir(os.environ.get("CUPS_SERVERROOT", "")) :
-            if len(sys.argv) == 7 :
-                inputfile = sys.argv[6]
-            else :    
-                inputfile = None
-                
-            # check that the DEVICE_URI environment variable's value is 
-            # prefixed with "cupspykota:" otherwise don't touch it.
-            # If this is the case, we have to remove the prefix from 
-            # the environment before launching the real backend in cupspykota
-            device_uri = os.environ.get("DEVICE_URI", "")
-            if device_uri.startswith("cupspykota:") :
-                fulldevice_uri = device_uri[:]
-                device_uri = fulldevice_uri[len("cupspykota:"):]
-                if device_uri.startswith("//") :    # lpd (at least)
-                    device_uri = device_uri[2:]
-                os.environ["DEVICE_URI"] = device_uri   # TODO : side effect !
-            # TODO : check this for more complex urls than ipp://myprinter.dot.com:631/printers/lp
-            try :
-                (backend, destination) = device_uri.split(":", 1) 
-            except ValueError :    
-                raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri
-            while destination.startswith("/") :
-                destination = destination[1:]
-            checkauth = destination.split("@", 1)    
-            if len(checkauth) == 2 :
-                destination = checkauth[1]
-            printerhostname = destination.split("/")[0].split(":")[0]
-            return ("CUPS", \
-                    printerhostname, \
-                    os.environ.get("PRINTER"), \
-                    sys.argv[2].strip(), \
-                    sys.argv[1].strip(), \
-                    inputfile, \
-                    int(sys.argv[4].strip()), \
-                    sys.argv[3], \
-                    sys.argv[5], \
-                    backend)
-        else :    
-            # Try to detect LPRng
-            # TODO : try to extract filename, options if available
-            jseen = Jseen = Pseen = nseen = rseen = Kseen = None
-            for arg in sys.argv :
-                if arg.startswith("-j") :
-                    jseen = arg[2:].strip()
-                elif arg.startswith("-n") :     
-                    nseen = arg[2:].strip()
-                elif arg.startswith("-P") :    
-                    Pseen = arg[2:].strip()
-                elif arg.startswith("-r") :    
-                    rseen = arg[2:].strip()
-                elif arg.startswith("-J") :    
-                    Jseen = arg[2:].strip()
-                elif arg.startswith("-K") or arg.startswith("-#") :    
-                    Kseen = int(arg[2:].strip())
-            if Kseen is None :        
-                Kseen = 1       # we assume the user wants at least one copy...
-            if (rseen is None) and jseen and Pseen and nseen :    
-                lparg = [arg for arg in "".join(os.environ.get("PRINTCAP_ENTRY", "").split()).split(":") if arg.startswith("rm=") or arg.startswith("lp=")]
-                try :
-                    rseen = lparg[0].split("=")[-1].split("@")[-1].split("%")[0]
-                except :    
-                    # Not found
-                    self.printInfo(_("Printer hostname undefined, set to 'localhost'"), "warn")
-                    rseen = "localhost"
-                
-            spooldir = os.environ.get("SPOOL_DIR", ".")    
-            df_name = os.environ.get("DATAFILES")
-            if not df_name :
-                try : 
-                    df_name = [line[10:] for line in os.environ.get("HF", "").split() if line.startswith("datafiles=")][0]
-                except IndexError :    
-                    try :    
-                        df_name = [line[8:] for line in os.environ.get("HF", "").split() if line.startswith("df_name=")][0]
-                    except IndexError :
-                        try :
-                            cftransfername = [line[15:] for line in os.environ.get("HF", "").split() if line.startswith("cftransfername=")][0]
-                        except IndexError :    
-                            try :
-                                df_name = [line[1:] for line in os.environ.get("CONTROL", "").split() if line.startswith("fdf") or line.startswith("Udf")][0]
-                            except IndexError :    
-                                raise PyKotaToolError, "Unable to find the file which holds the job's datas. Please file a bug report for PyKota."
-                            else :    
-                                inputfile = os.path.join(spooldir, df_name) # no need to strip()
-                        else :    
-                            inputfile = os.path.join(spooldir, "d" + cftransfername[1:]) # no need to strip()
-                    else :    
-                        inputfile = os.path.join(spooldir, df_name) # no need to strip()
-                else :    
-                    inputfile = os.path.join(spooldir, df_name) # no need to strip()
-            else :    
-                inputfile = os.path.join(spooldir, df_name.strip())
-                
-            if jseen and Pseen and nseen and rseen :        
-                options = os.environ.get("HF", "") or os.environ.get("CONTROL", "")
-                return ("LPRNG", rseen, Pseen, nseen, jseen, inputfile, Kseen, Jseen, options, None)
-        self.printInfo(_("Printing system unknown, args=%s") % " ".join(sys.argv), "warn")
-        return (None, None, None, None, None, None, None, None, None, None)   # Unknown printing system
-        
-    def getPrinterUserAndUserPQuota(self) :        
-        """Returns a tuple (policy, printer, user, and user print quota) on this printer.
-        
-           "OK" is returned in the policy if both printer, user and user print quota
-           exist in the Quota Storage.
-           Otherwise, the policy as defined for this printer in pykota.conf is returned.
-           
-           If policy was set to "EXTERNAL" and one of printer, user, or user print quota
-           doesn't exist in the Quota Storage, then an external command is launched, as
-           defined in the external policy for this printer in pykota.conf
-           This external command can do anything, like automatically adding printers
-           or users, for example, and finally extracting printer, user and user print
-           quota from the Quota Storage is tried a second time.
-           
-           "EXTERNALERROR" is returned in case policy was "EXTERNAL" and an error status
-           was returned by the external command.
-        """
-        for passnumber in range(1, 3) :
-            printer = self.storage.getPrinter(self.printername)
-            user = self.storage.getUser(self.username)
-            userpquota = self.storage.getUserPQuota(user, printer)
-            if printer.Exists and user.Exists and userpquota.Exists :
-                policy = "OK"
-                break
-            (policy, args) = self.config.getPrinterPolicy(self.printername)
-            if policy == "EXTERNAL" :    
-                commandline = self.formatCommandLine(args, user, printer)
-                if not printer.Exists :
-                    self.printInfo(_("Printer %s not registered in the PyKota system, applying external policy (%s) for printer %s") % (self.printername, commandline, self.printername))
-                if not user.Exists :
-                    self.printInfo(_("User %s not registered in the PyKota system, applying external policy (%s) for printer %s") % (self.username, commandline, self.printername))
-                if not userpquota.Exists :
-                    self.printInfo(_("User %s doesn't have quota on printer %s in the PyKota system, applying external policy (%s) for printer %s") % (self.username, self.printername, commandline, self.printername))
-                if os.system(commandline) :
-                    self.printInfo(_("External policy %s for printer %s produced an error. Job rejected. Please check PyKota's configuration files.") % (commandline, self.printername), "error")
-                    policy = "EXTERNALERROR"
-                    break
-            else :        
-                if not printer.Exists :
-                    self.printInfo(_("Printer %s not registered in the PyKota system, applying default policy (%s)") % (self.printername, policy))
-                if not user.Exists :
-                    self.printInfo(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (self.username, policy, self.printername))
-                if not userpquota.Exists :
-                    self.printInfo(_("User %s doesn't have quota on printer %s in the PyKota system, applying default policy (%s)") % (self.username, self.printername, policy))
-                break
-        if policy == "EXTERNAL" :    
-            if not printer.Exists :
-                self.printInfo(_("Printer %s still not registered in the PyKota system, job will be rejected") % self.printername)
-            if not user.Exists :
-                self.printInfo(_("User %s still not registered in the PyKota system, job will be rejected on printer %s") % (self.username, self.printername))
-            if not userpquota.Exists :
-                self.printInfo(_("User %s still doesn't have quota on printer %s in the PyKota system, job will be rejected") % (self.username, self.printername))
-        return (policy, printer, user, userpquota)
-        
-    def mainWork(self) :    
-        """Main work is done here."""
-        (policy, printer, user, userpquota) = self.getPrinterUserAndUserPQuota()
-        # TODO : check for last user's quota in case pykota filter is used with querying
-        if policy == "EXTERNALERROR" :
-            # Policy was 'EXTERNAL' and the external command returned an error code
-            return self.removeJob()
-        elif policy == "EXTERNAL" :
-            # Policy was 'EXTERNAL' and the external command wasn't able
-            # to add either the printer, user or user print quota
-            return self.removeJob()
-        elif policy == "DENY" :    
-            # Either printer, user or user print quota doesn't exist,
-            # and the job should be rejected.
-            return self.removeJob()
-        else :
-            if policy not in ("OK", "ALLOW") :
-                self.printInfo(_("Invalid policy %s for printer %s") % (policy, self.printername))
-                return self.removeJob()
-            else :
-                return self.doWork(policy, printer, user, userpquota)
