Changeset 3413 for pykota/trunk/pykota/accounters
- Timestamp:
- 09/27/08 22:02:37 (16 years ago)
- Location:
- pykota/trunk/pykota/accounters
- Files:
-
- 6 modified
Legend:
- Unmodified
- Added
- Removed
-
pykota/trunk/pykota/accounters/hardware.py
r3411 r3413 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 36 36 AccounterBase.__init__(self, kotabackend, arguments, ispreaccounter, name) 37 37 self.isSoftware = 0 38 39 def getPrinterInternalPageCounter(self) : 38 39 def getPrinterInternalPageCounter(self) : 40 40 """Returns the printer's internal page counter.""" 41 41 self.filter.logdebug("Reading printer %s's internal page counter..." % self.filter.PrinterName) 42 42 counter = self.askPrinterPageCounter(self.filter.PrinterHostName) 43 43 self.filter.logdebug("Printer %s's internal page counter value is : %s" % (self.filter.PrinterName, str(counter))) 44 return counter 45 46 def beginJob(self, printer) : 44 return counter 45 46 def beginJob(self, printer) : 47 47 """Saves printer internal page counter at start of job.""" 48 48 # save page counter before job 49 49 self.LastPageCounter = self.getPrinterInternalPageCounter() 50 50 self.fakeBeginJob() 51 52 def fakeBeginJob(self) : 51 52 def fakeBeginJob(self) : 53 53 """Fakes a begining of a job.""" 54 54 self.counterbefore = self.getLastPageCounter() 55 56 def endJob(self, printer) : 55 56 def endJob(self, printer) : 57 57 """Saves printer internal page counter at end of job.""" 58 58 # save page counter after job 59 59 self.LastPageCounter = self.counterafter = self.getPrinterInternalPageCounter() 60 61 def getJobSize(self, printer) : 60 61 def getJobSize(self, printer) : 62 62 """Returns the actual job size.""" 63 63 if (not self.counterbefore) or (not self.counterafter) : … … 67 67 # if there's a previous job, use the last value from database 68 68 self.filter.printInfo(_("Retrieving printer %s's page counter from database instead.") % printer.Name, "warn") 69 if not self.counterbefore : 69 if not self.counterbefore : 70 70 self.counterbefore = printer.LastJob.PrinterPageCounter or 0 71 71 if not self.counterafter : 72 72 self.counterafter = printer.LastJob.PrinterPageCounter or 0 73 before = min(self.counterbefore, self.counterafter) 74 after = max(self.counterbefore, self.counterafter) 73 before = min(self.counterbefore, self.counterafter) 74 after = max(self.counterbefore, self.counterafter) 75 75 self.counterbefore = before 76 76 self.counterafter = after … … 85 85 self.counterbefore = 0 86 86 self.counterafter = 1 87 88 jobsize = (self.counterafter - self.counterbefore) 87 88 jobsize = (self.counterafter - self.counterbefore) 89 89 if jobsize < 0 : 90 # Try to take care of HP printers 90 # Try to take care of HP printers 91 91 # Their internal page counter is saved to NVRAM 92 92 # only every 10 pages. If the printer was switched 93 93 # off then back on during the job, and that the 94 # counters difference is negative, we know 94 # counters difference is negative, we know 95 95 # the formula (we can't know if more than eleven 96 96 # pages were printed though) : 97 97 if jobsize > -10 : 98 98 jobsize += 10 99 else : 99 else : 100 100 # here we may have got a printer being replaced 101 101 # DURING the job. This is HIGHLY improbable (but already happened) ! … … 104 104 jobsize = 1 105 105 return jobsize 106 106 107 107 def askPrinterPageCounter(self, printer) : 108 108 """Returns the page counter from the printer via an external command. 109 109 110 110 The external command must report the life time page number of the printer on stdout. 111 111 """ … … 117 117 elif (cmdlower == "pjl") or cmdlower.startswith("pjl:") : 118 118 return pjl.Handler(self, printer, skipinitialwait).retrieveInternalPageCounter() 119 119 120 120 if printer is None : 121 121 raise PyKotaAccounterError, _("Unknown printer address in HARDWARE(%s) for printer %s") % (commandline, self.filter.PrinterName) 122 while 1 : 122 while 1 : 123 123 self.filter.printInfo(_("Launching HARDWARE(%s)...") % commandline) 124 124 pagecounter = None 125 child = popen2.Popen4(commandline) 125 child = popen2.Popen4(commandline) 126 126 try : 127 127 answer = child.fromchild.read() 128 except IOError : 128 except IOError : 129 129 # we were interrupted by a signal, certainely a SIGTERM 130 130 # caused by the user cancelling the current job 131 131 try : 132 132 os.kill(child.pid, signal.SIGTERM) 133 except : 133 except : 134 134 pass # already killed ? 135 135 self.filter.printInfo(_("SIGTERM was sent to hardware accounter %s (pid: %s)") % (commandline, child.pid)) 136 else : 136 else : 137 137 lines = [l.strip() for l in answer.split("\n")] 138 for i in range(len(lines)) : 138 for i in range(len(lines)) : 139 139 try : 140 140 pagecounter = int(lines[i]) 141 141 except (AttributeError, ValueError) : 142 142 self.filter.printInfo(_("Line [%s] skipped in accounter's output. Trying again...") % lines[i]) 143 else : 143 else : 144 144 break 145 child.fromchild.close() 145 child.fromchild.close() 146 146 child.tochild.close() 147 147 try : 148 148 status = child.wait() 149 except OSError, msg : 149 except OSError, msg : 150 150 self.filter.logdebug("Error while waiting for hardware accounter pid %s : %s" % (child.pid, msg)) 151 else : 151 else : 152 152 if os.WIFEXITED(status) : 153 153 status = os.WEXITSTATUS(status) 154 154 self.filter.printInfo(_("Hardware accounter %s exit code is %s") % (self.arguments, str(status))) 155 155 156 156 if pagecounter is None : 157 157 message = _("Unable to query printer %s via HARDWARE(%s)") % (printer, commandline) … … 159 159 self.filter.printInfo(message, "error") 160 160 else : 161 raise PyKotaAccounterError, message 162 else : 163 return pagecounter 161 raise PyKotaAccounterError, message 162 else : 163 return pagecounter -
pykota/trunk/pykota/accounters/__init__.py
r3411 r3413 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. -
pykota/trunk/pykota/accounters/ink.py
r3411 r3413 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 36 36 "GC" : { "G" : "grayscale", "C" : "colored" } , 37 37 } 38 def computeJobSize(self) : 38 def computeJobSize(self) : 39 39 """Do ink accounting for a print job.""" 40 40 if (not self.isPreAccounter) and \ … … 46 46 self.inkUsage = self.filter.preaccounter.inkUsage # Optimize : already computed ! 47 47 return self.filter.softwareJobSize # Optimize : already computed ! 48 48 49 49 parameters = [p.strip() for p in self.arguments.split(',')] 50 50 if len(parameters) == 1 : … … 54 54 if colorspace not in ("cmyk", "bw", "cmy", "rgb", "gc") : 55 55 raise PyKotaAccounterError, _("Invalid parameters for ink accounter : [%s]") % self.arguments 56 57 try : 56 57 try : 58 58 resolution = int(resolution) 59 except ValueError : 59 except ValueError : 60 60 raise PyKotaAccounterError, "Invalid parameters for ink accounter : [%s]" % self.arguments 61 61 62 62 self.filter.logdebug("Using internal parser to compute job's size and ink usage.") 63 63 64 64 jobsize = 0 65 65 if self.filter.JobSizeBytes : 66 66 try : 67 67 from pkpgpdls import analyzer, pdlparser 68 except ImportError : 68 except ImportError : 69 69 self.filter.printInfo("pkpgcounter is now distributed separately, please grab it from http://www.pykota.com/software/pkpgcounter", "error") 70 70 self.filter.printInfo("Precomputed job size will be forced to 0 pages.", "error") 71 else : 71 else : 72 72 options = analyzer.AnalyzerOptions(colorspace=colorspace, resolution=resolution) 73 73 try : 74 74 parser = analyzer.PDLAnalyzer(self.filter.DataFile, options) 75 75 (cspace, pages) = parser.getInkCoverage() 76 except pdlparser.PDLParserError, msg : 76 except pdlparser.PDLParserError, msg : 77 77 # Here we just log the failure, but 78 78 # we finally ignore it and return 0 since this … … 80 80 # job's size MAY be. 81 81 self.filter.printInfo(_("Unable to precompute the job's size and ink coverage with the generic PDL analyzer : %s") % msg, "warn") 82 else : 82 else : 83 83 cspacelabels = self.cspaceExpanded[cspace] 84 84 for page in pages : … … 86 86 for color in page.keys() : 87 87 colordict[cspacelabels[color]] = page[color] 88 self.inkUsage.append(colordict) 88 self.inkUsage.append(colordict) 89 89 jobsize = len(pages) 90 90 try : 91 91 if self.filter.Ticket.FileName is not None : 92 # when a filename is passed as an argument, the backend 92 # when a filename is passed as an argument, the backend 93 93 # must generate the correct number of copies. 94 94 jobsize *= self.filter.Ticket.Copies 95 95 self.inkUsage *= self.filter.Ticket.Copies 96 except AttributeError : # When not run from the cupspykota backend 96 except AttributeError : # When not run from the cupspykota backend 97 97 pass 98 98 self.filter.logdebug("Ink usage : %s ===> %s" % (cspace, repr(self.inkUsage))) 99 return jobsize 99 return jobsize -
pykota/trunk/pykota/accounters/pjl.py
r3411 r3413 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 21 21 # 22 22 23 """This module defines the necessary classes and methods to retrieve 24 a printer's internal page counter over a TCP connection.""" 23 """This module defines the necessary classes and methods to retrieve 24 a printer's internal page counter over a TCP connection.""" 25 25 26 26 import sys … … 36 36 37 37 # Old method : PJLMESSAGE = "\033%-12345X@PJL USTATUSOFF\r\n@PJL INFO STATUS\r\n@PJL INFO PAGECOUNT\r\n\033%-12345X" 38 # Here's a new method, which seems to work fine on my HP2300N, while the 38 # Here's a new method, which seems to work fine on my HP2300N, while the 39 39 # previous one didn't. 40 # TODO : We could also experiment with USTATUS JOB=ON and we would know for sure 40 # TODO : We could also experiment with USTATUS JOB=ON and we would know for sure 41 41 # when the job is finished, without having to poll the printer repeatedly. 42 42 PJLMESSAGE = "\033%-12345X@PJL USTATUS DEVICE=ON\r\n@PJL INFO STATUS\r\n@PJL INFO PAGECOUNT\r\n@PJL USTATUS DEVICE=OFF\033%-12345X" … … 52 52 "40000" : "Sleep Mode", # Standby 53 53 } 54 54 55 55 class Handler : 56 56 """A class for PJL print accounting.""" … … 69 69 self.readthread = None 70 70 self.quitEvent = threading.Event() 71 72 def __del__(self) : 71 72 def __del__(self) : 73 73 """Ensures the network connection is closed at object deletion time.""" 74 74 self.close() 75 76 def open(self) : 75 76 def open(self) : 77 77 """Opens the network connection.""" 78 78 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) … … 93 93 self.parent.filter.logdebug("Connected to printer %s:%s" % (self.printerHostname, self.port)) 94 94 return True 95 96 def close(self) : 95 96 def close(self) : 97 97 """Closes the network connection.""" 98 98 if not self.closed : … … 107 107 self.queue = None 108 108 self.closed = True 109 110 def readloop(self) : 109 110 def readloop(self) : 111 111 """Reading loop thread.""" 112 112 self.parent.filter.logdebug("Reading thread started.") … … 115 115 try : 116 116 answer = self.sock.recv(1) 117 except socket.timeout : 117 except socket.timeout : 118 118 pass 119 119 except socket.error, (dummy, msg) : 120 120 self.parent.filter.printInfo(_("Problem while receiving PJL answer from %s:%s : %s") % (self.printerHostname, self.port, str(msg)), "warn") 121 else : 121 else : 122 122 if answer : 123 123 readbuffer.append(answer) … … 125 125 self.queue.put("".join(readbuffer)) 126 126 readbuffer = [] 127 if readbuffer : 128 self.queue.put("".join(readbuffer)) 127 if readbuffer : 128 self.queue.put("".join(readbuffer)) 129 129 self.parent.filter.logdebug("Reading thread ended.") 130 131 def retrievePJLValues(self) : 130 131 def retrievePJLValues(self) : 132 132 """Retrieves a printer's internal page counter and status via PJL.""" 133 133 while not self.open() : … … 141 141 except socket.error, msg : 142 142 self.parent.filter.printInfo(_("Problem while sending PJL query to %s:%s : %s") % (self.printerHostname, self.port, str(msg)), "warn") 143 else : 143 else : 144 144 self.parent.filter.logdebug("Query sent to %s : %s" % (self.printerHostname, repr(PJLMESSAGE))) 145 145 actualpagecount = self.printerStatus = None … … 147 147 try : 148 148 answer = self.queue.get(True, 5) 149 except Queue.Empty : 149 except Queue.Empty : 150 150 self.parent.filter.logdebug("Timeout when reading printer's answer from %s:%s" % (self.printerHostname, self.port)) 151 else : 151 else : 152 152 readnext = False 153 153 self.parent.filter.logdebug("PJL answer : %s" % repr(answer)) 154 for line in [l.strip() for l in answer.split()] : 154 for line in [l.strip() for l in answer.split()] : 155 155 if line.startswith("CODE=") : 156 156 self.printerStatus = line.split("=")[1] 157 157 self.parent.filter.logdebug("Found status : %s" % self.printerStatus) 158 elif line.startswith("PAGECOUNT=") : 158 elif line.startswith("PAGECOUNT=") : 159 159 try : 160 160 actualpagecount = int(line.split('=')[1].strip()) 161 except ValueError : 161 except ValueError : 162 162 self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip()) 163 163 else : 164 164 self.parent.filter.logdebug("Found pages counter : %s" % actualpagecount) 165 elif line.startswith("PAGECOUNT") : 165 elif line.startswith("PAGECOUNT") : 166 166 readnext = True # page counter is on next line 167 elif readnext : 167 elif readnext : 168 168 try : 169 169 actualpagecount = int(line.strip()) 170 except ValueError : 170 except ValueError : 171 171 self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip()) 172 172 else : … … 174 174 readnext = False 175 175 self.printerInternalPageCounter = max(actualpagecount, self.printerInternalPageCounter) 176 finally : 176 finally : 177 177 self.close() 178 178 179 179 def waitPrinting(self) : 180 180 """Waits for printer status being 'printing'.""" … … 183 183 if not noprintingmaxdelay : 184 184 self.parent.filter.logdebug("Will wait indefinitely until printer %s is in 'printing' state." % self.parent.filter.PrinterName) 185 else : 185 else : 186 186 self.parent.filter.logdebug("Will wait until printer %s is in 'printing' state or %i seconds have elapsed." % (self.parent.filter.PrinterName, noprintingmaxdelay)) 187 187 previousValue = self.parent.getLastPageCounter() … … 192 192 if self.printerStatus in ('10023', '10003') : 193 193 break 194 if self.printerInternalPageCounter is not None : 194 if self.printerInternalPageCounter is not None : 195 195 if firstvalue is None : 196 196 # first time we retrieved a page counter, save it 197 197 firstvalue = self.printerInternalPageCounter 198 else : 198 else : 199 199 # second time (or later) 200 200 if firstvalue < self.printerInternalPageCounter : … … 213 213 # the printer rejected it for some reason. 214 214 self.parent.filter.printInfo("Printer %s probably won't print this job !!!" % self.parent.filter.PrinterName, "warn") 215 else : 215 else : 216 216 # Here the job has already been entirely printed, and 217 217 # the printer has already passed from 'idle' to 'printing' to 'idle' again. … … 220 220 self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.PrinterName) 221 221 time.sleep(statusstabilizationdelay) 222 222 223 223 def waitIdle(self) : 224 224 """Waits for printer status being 'idle'.""" … … 233 233 and (os.environ.get("PYKOTAPHASE") == "BEFORE") : 234 234 self.parent.filter.logdebug("No need to wait for the printer to be idle, it is the case already.") 235 return 235 return 236 236 idle_num += 1 237 237 if idle_num >= statusstabilizationloops : 238 238 # printer status is stable, we can exit 239 239 break 240 else : 240 else : 241 241 idle_num = 0 242 242 self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.PrinterName) 243 243 time.sleep(statusstabilizationdelay) 244 244 245 245 def retrieveInternalPageCounter(self) : 246 246 """Returns the page counter from the printer via internal PJL handling.""" … … 251 251 self.parent.filter.JobSizeBytes : 252 252 self.waitPrinting() 253 self.waitIdle() 254 except : 253 self.waitIdle() 254 except : 255 255 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") 256 256 raise 257 else : 257 else : 258 258 return self.printerInternalPageCounter 259 259 260 260 def main(hostname) : 261 261 """Tries PJL accounting for a printer host.""" … … 266 266 self.PrinterName = "FakePrintQueue" 267 267 self.JobSizeBytes = 1 268 268 269 269 def printInfo(self, msg, level="info") : 270 270 """Prints informational message.""" 271 271 sys.stderr.write("%s : %s\n" % (level.upper(), msg)) 272 272 sys.stderr.flush() 273 274 def logdebug(self, msg) : 273 274 def logdebug(self, msg) : 275 275 """Prints debug message.""" 276 276 self.printInfo(msg, "debug") 277 278 class FakeAccounter : 277 278 class FakeAccounter : 279 279 """Fakes an accounter for testing purposes.""" 280 280 def __init__(self, hostname) : … … 283 283 self.filter = FakeFilter() 284 284 self.protocolHandler = Handler(self, hostname) 285 286 def getLastPageCounter(self) : 285 286 def getLastPageCounter(self) : 287 287 """Fakes the return of a page counter.""" 288 288 return 0 289 289 290 290 acc = FakeAccounter(hostname) 291 291 return acc.protocolHandler.retrieveInternalPageCounter() 292 293 if __name__ == "__main__" : 294 if len(sys.argv) != 2 : 292 293 if __name__ == "__main__" : 294 if len(sys.argv) != 2 : 295 295 sys.stderr.write("Usage : python %s printer_ip_address\n" % sys.argv[0]) 296 else : 296 else : 297 297 def _(msg) : 298 298 """Fake gettext method.""" 299 299 return msg 300 300 301 301 pagecounter = main(sys.argv[1]) 302 302 print "Internal page counter's value is : %s" % pagecounter 303 303 -
pykota/trunk/pykota/accounters/snmp.py
r3411 r3413 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 36 36 try : 37 37 from pysnmp.entity.rfc3413.oneliner import cmdgen 38 except ImportError : 38 except ImportError : 39 39 hasV4 = False 40 40 try : … … 50 50 from pykota import constants 51 51 52 # 52 # 53 53 # Documentation taken from RFC 3805 (Printer MIB v2) and RFC 2790 (Host Resource MIB) 54 54 # … … 67 67 4 : 'testing', 68 68 5 : 'down', 69 } 69 } 70 70 hrPrinterDetectedErrorStateOID = "1.3.6.1.2.1.25.3.5.1.2.1" # SNMPv2-SMI::mib-2.25.3.5.1.2.1 71 71 printerDetectedErrorStateValues = [ { 128 : 'Low Paper', … … 87 87 1 : 'Not Assigned in RFC3805', 88 88 }, 89 ] 90 89 ] 90 91 91 # The default error mask to use when checking error conditions. 92 92 defaultErrorMask = 0x4fcc # [ 'No Paper', … … 100 100 # 'Input Tray Empty', 101 101 # ] 102 103 # WARNING : some printers don't support this one : 102 103 # WARNING : some printers don't support this one : 104 104 prtConsoleDisplayBufferTextOID = "1.3.6.1.2.1.43.16.5.1.2.1.1" # SNMPv2-SMI::mib-2.43.16.5.1.2.1.1 105 105 class BaseHandler : … … 111 111 try : 112 112 self.community = self.parent.arguments.split(":")[1].strip() 113 except IndexError : 113 except IndexError : 114 114 self.community = "public" 115 115 self.port = 161 116 116 self.initValues() 117 118 def initValues(self) : 117 118 def initValues(self) : 119 119 """Initializes SNMP values.""" 120 120 self.printerInternalPageCounter = None … … 123 123 self.printerDetectedErrorState = None 124 124 self.timebefore = time.time() # resets timer also in case of error 125 126 def retrieveSNMPValues(self) : 125 126 def retrieveSNMPValues(self) : 127 127 """Retrieves a printer's internal page counter and status via SNMP.""" 128 128 raise RuntimeError, "You have to overload this method." 129 130 def extractErrorStates(self, value) : 129 130 def extractErrorStates(self, value) : 131 131 """Returns a list of textual error states from a binary value.""" 132 132 states = [] … … 137 137 if byte & k : 138 138 states.append(v) 139 return states 140 141 def checkIfError(self, errorstates) : 139 return states 140 141 def checkIfError(self, errorstates) : 142 142 """Checks if any error state is fatal or not.""" 143 143 if errorstates is None : … … 146 146 try : 147 147 errormask = self.parent.filter.config.getPrinterSNMPErrorMask(self.parent.filter.PrinterName) 148 except AttributeError : # debug mode 148 except AttributeError : # debug mode 149 149 errormask = defaultErrorMask 150 150 if errormask is None : … … 161 161 return True 162 162 self.parent.filter.logdebug("No error condition matching mask 0x%04x" % errormask) 163 return False 164 163 return False 164 165 165 def waitPrinting(self) : 166 166 """Waits for printer status being 'printing'.""" … … 169 169 if not noprintingmaxdelay : 170 170 self.parent.filter.logdebug("Will wait indefinitely until printer %s is in 'printing' state." % self.parent.filter.PrinterName) 171 else : 171 else : 172 172 self.parent.filter.logdebug("Will wait until printer %s is in 'printing' state or %i seconds have elapsed." % (self.parent.filter.PrinterName, noprintingmaxdelay)) 173 173 previousValue = self.parent.getLastPageCounter() … … 178 178 if statusAsString in ('printing', 'warmup') : 179 179 break 180 if self.printerInternalPageCounter is not None : 180 if self.printerInternalPageCounter is not None : 181 181 if firstvalue is None : 182 182 # first time we retrieved a page counter, save it 183 183 firstvalue = self.printerInternalPageCounter 184 else : 184 else : 185 185 # second time (or later) 186 186 if firstvalue < self.printerInternalPageCounter : … … 205 205 # the printer rejected it for some reason. 206 206 self.parent.filter.printInfo("Printer %s probably won't print this job !!!" % self.parent.filter.PrinterName, "warn") 207 else : 207 else : 208 208 # Here the job has already been entirely printed, and 209 209 # the printer has already passed from 'idle' to 'printing' to 'idle' again. 210 210 self.parent.filter.printInfo("Printer %s has probably already printed this job !!!" % self.parent.filter.PrinterName, "warn") 211 211 break 212 self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.PrinterName) 212 self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.PrinterName) 213 213 time.sleep(statusstabilizationdelay) 214 214 215 215 def waitIdle(self) : 216 216 """Waits for printer status being 'idle'.""" … … 228 228 (dstatusAsString == 'running'))) : 229 229 idle_flag = 1 # Standby / Powersave is considered idle 230 if idle_flag : 230 if idle_flag : 231 231 if (self.printerInternalPageCounter is not None) \ 232 232 and self.skipinitialwait \ 233 233 and (os.environ.get("PYKOTAPHASE") == "BEFORE") : 234 234 self.parent.filter.logdebug("No need to wait for the printer to be idle, it is the case already.") 235 return 235 return 236 236 idle_num += 1 237 237 if idle_num >= statusstabilizationloops : 238 238 # printer status is stable, we can exit 239 239 break 240 else : 240 else : 241 241 idle_num = 0 242 self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.PrinterName) 242 self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.PrinterName) 243 243 time.sleep(statusstabilizationdelay) 244 244 245 245 def retrieveInternalPageCounter(self) : 246 246 """Returns the page counter from the printer via internal SNMP handling.""" … … 251 251 self.parent.filter.JobSizeBytes : 252 252 self.waitPrinting() 253 self.waitIdle() 254 except : 253 self.waitIdle() 254 except : 255 255 self.parent.filter.printInfo(_("SNMP querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (self.printerInternalPageCounter, self.parent.filter.PrinterName), "warn") 256 256 raise 257 257 return self.printerInternalPageCounter 258 259 if hasV4 : 258 259 if hasV4 : 260 260 class Handler(BaseHandler) : 261 261 """A class for pysnmp v4.x""" … … 270 270 tuple([int(i) for i in hrDeviceStatusOID.split('.')]), \ 271 271 tuple([int(i) for i in hrPrinterDetectedErrorStateOID.split('.')])) 272 except socket.gaierror, msg : 272 except socket.gaierror, msg : 273 273 errorIndication = repr(msg) 274 except : 274 except : 275 275 errorIndication = "Unknown SNMP/Network error. Check your wires." 276 if errorIndication : 276 if errorIndication : 277 277 self.parent.filter.printInfo("SNMP Error : %s" % errorIndication, "error") 278 278 self.initValues() 279 elif errorStatus : 279 elif errorStatus : 280 280 self.parent.filter.printInfo("SNMP Error : %s at %s" % (errorStatus.prettyPrint(), \ 281 281 varBinds[int(errorIndex)-1]), \ 282 282 "error") 283 283 self.initValues() 284 else : 284 else : 285 285 self.printerInternalPageCounter = max(self.printerInternalPageCounter, int(varBinds[0][1].prettyPrint() or "0")) 286 286 self.printerStatus = int(varBinds[1][1].prettyPrint()) … … 295 295 class Handler(BaseHandler) : 296 296 """A class for pysnmp v3.4.x""" 297 def retrieveSNMPValues(self) : 297 def retrieveSNMPValues(self) : 298 298 """Retrieves a printer's internal page counter and status via SNMP.""" 299 299 ver = alpha.protoVersions[alpha.protoVersionId1] … … 310 310 (self.printerHostname, self.port), \ 311 311 (self.handleAnswer, req)) 312 except (SnmpOverUdpError, select.error), msg : 312 except (SnmpOverUdpError, select.error), msg : 313 313 self.parent.filter.printInfo(_("Network error while doing SNMP queries on printer %s : %s") % (self.printerHostname, msg), "warn") 314 314 self.initValues() 315 315 tsp.close() 316 316 317 317 def handleAnswer(self, wholeMsg, notusedhere, req): 318 318 """Decodes and handles the SNMP answer.""" … … 321 321 try : 322 322 rsp.berDecode(wholeMsg) 323 except TypeMismatchError, msg : 323 except TypeMismatchError, msg : 324 324 self.parent.filter.printInfo(_("SNMP message decoding error for printer %s : %s") % (self.printerHostname, msg), "warn") 325 325 self.initValues() … … 333 333 for varBind in rsp.apiAlphaGetPdu().apiAlphaGetVarBindList(): 334 334 self.values.append(varBind.apiAlphaGetOidVal()[1].rawAsn1Value) 335 try : 335 try : 336 336 # keep maximum value seen for printer's internal page counter 337 337 self.printerInternalPageCounter = max(self.printerInternalPageCounter, self.values[0]) … … 344 344 deviceStatusValues.get(self.deviceStatus), \ 345 345 self.printerDetectedErrorState)) 346 except IndexError : 346 except IndexError : 347 347 self.parent.filter.logdebug("SNMP answer is incomplete : %s" % str(self.values)) 348 348 pass 349 else : 349 else : 350 350 return 1 351 351 352 352 def main(hostname) : 353 353 """Tries SNMP accounting for a printer host.""" … … 358 358 self.PrinterName = "FakePrintQueue" 359 359 self.JobSizeBytes = 1 360 360 361 361 def printInfo(self, msg, level="info") : 362 362 """Prints informational message.""" 363 363 sys.stderr.write("%s : %s\n" % (level.upper(), msg)) 364 364 sys.stderr.flush() 365 366 def logdebug(self, msg) : 365 366 def logdebug(self, msg) : 367 367 """Prints debug message.""" 368 368 self.printInfo(msg, "debug") 369 370 class fakeAccounter : 369 370 class fakeAccounter : 371 371 """Fakes an accounter for testing purposes.""" 372 372 def __init__(self) : … … 375 375 self.filter = fakeFilter() 376 376 self.protocolHandler = Handler(self, hostname) 377 378 def getLastPageCounter(self) : 377 378 def getLastPageCounter(self) : 379 379 """Fakes the return of a page counter.""" 380 380 return 0 381 382 acc = fakeAccounter() 381 382 acc = fakeAccounter() 383 383 return acc.protocolHandler.retrieveInternalPageCounter() 384 385 if __name__ == "__main__" : 386 if len(sys.argv) != 2 : 384 385 if __name__ == "__main__" : 386 if len(sys.argv) != 2 : 387 387 sys.stderr.write("Usage : python %s printer_ip_address\n" % sys.argv[0]) 388 else : 388 else : 389 389 def _(msg) : 390 390 return msg 391 391 392 392 pagecounter = main(sys.argv[1]) 393 393 print "Internal page counter's value is : %s" % pagecounter -
pykota/trunk/pykota/accounters/software.py
r3411 r3413 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 30 30 31 31 class Accounter(AccounterBase) : 32 def computeJobSize(self) : 32 def computeJobSize(self) : 33 33 """Feeds an external command with our datas to let it compute the job size, and return its value.""" 34 34 if (not self.isPreAccounter) and \ … … 39 39 self.filter.logdebug("Precomputing pass told us that job is %s pages long." % self.filter.softwareJobSize) 40 40 return self.filter.softwareJobSize # Optimize : already computed ! 41 41 42 42 if self.arguments : 43 43 self.filter.logdebug("Using external script %s to compute job's size." % self.arguments) 44 44 return self.withExternalScript() 45 else : 45 else : 46 46 self.filter.logdebug("Using internal parser to compute job's size.") 47 47 return self.withInternalParser() 48 49 def withInternalParser(self) : 48 49 def withInternalParser(self) : 50 50 """Does software accounting through an external script.""" 51 51 jobsize = 0 … … 53 53 try : 54 54 from pkpgpdls import analyzer, pdlparser 55 except ImportError : 55 except ImportError : 56 56 self.filter.printInfo("pkpgcounter is now distributed separately, please grab it from http://www.pykota.com/software/pkpgcounter", "error") 57 57 self.filter.printInfo("Precomputed job size will be forced to 0 pages.", "error") 58 else : 58 else : 59 59 try : 60 60 parser = analyzer.PDLAnalyzer(self.filter.DataFile) 61 61 jobsize = parser.getJobSize() 62 except pdlparser.PDLParserError, msg : 62 except pdlparser.PDLParserError, msg : 63 63 # Here we just log the failure, but 64 64 # we finally ignore it and return 0 since this … … 66 66 # job's size MAY be. 67 67 self.filter.printInfo(_("Unable to precompute the job's size with the generic PDL analyzer : %s") % msg, "warn") 68 else : 68 else : 69 69 try : 70 70 if self.filter.Ticket.FileName is not None : 71 # when a filename is passed as an argument, the backend 71 # when a filename is passed as an argument, the backend 72 72 # must generate the correct number of copies. 73 73 jobsize *= self.filter.Ticket.Copies 74 74 except AttributeError : # When not run from the cupspykota backend 75 75 pass 76 return jobsize 77 78 def withExternalScript(self) : 76 return jobsize 77 78 def withExternalScript(self) : 79 79 """Does software accounting through an external script.""" 80 80 self.filter.logdebug(_("Launching SOFTWARE(%s)...") % self.arguments) … … 83 83 try : 84 84 answer = child.read() 85 except (IOError, OSError), msg : 86 msg = "%s : %s" % (self.arguments, msg) 85 except (IOError, OSError), msg : 86 msg = "%s : %s" % (self.arguments, msg) 87 87 self.filter.printInfo(_("Unable to compute job size with accounter %s") % msg, "warn") 88 else : 88 else : 89 89 lines = [l.strip() for l in answer.split("\n")] 90 for i in range(len(lines)) : 90 for i in range(len(lines)) : 91 91 try : 92 92 pagecounter = int(lines[i]) 93 93 except (AttributeError, ValueError) : 94 94 self.filter.logdebug(_("Line [%s] skipped in accounter's output. Trying again...") % lines[i]) 95 else : 95 else : 96 96 break 97 97 98 98 status = child.close() 99 99 try : 100 100 if os.WIFEXITED(status) : 101 101 status = os.WEXITSTATUS(status) 102 except TypeError : 102 except TypeError : 103 103 pass # None means no error occured. 104 104 self.filter.logdebug(_("Software accounter %s exit code is %s") % (self.arguments, str(status))) 105 106 if pagecounter is None : 105 106 if pagecounter is None : 107 107 message = _("Unable to compute job size with accounter %s") % self.arguments 108 108 if self.onerror == "CONTINUE" : … … 111 111 raise PyKotaAccounterError, message 112 112 self.filter.logdebug("Software accounter %s said job is %s pages long." % (self.arguments, repr(pagecounter))) 113 114 pagecounter = pagecounter or 0 113 114 pagecounter = pagecounter or 0 115 115 try : 116 116 if self.filter.Ticket.FileName is not None : 117 # when a filename is passed as an argument, the backend 117 # when a filename is passed as an argument, the backend 118 118 # must generate the correct number of copies. 119 119 pagecounter *= self.filter.Ticket.Copies 120 except AttributeError : 120 except AttributeError : 121 121 pass # when called from pykotme. TODO : clean this mess some day. 122 122 123 123 return pagecounter