35 | | from pysnmp.asn1.encoding.ber.error import TypeMismatchError |
36 | | from pysnmp.mapping.udp.error import SnmpOverUdpError |
37 | | from pysnmp.mapping.udp.role import Manager |
38 | | from pysnmp.proto.api import alpha |
39 | | except ImportError : |
40 | | class Handler : |
41 | | def __init__(self, parent, printerhostname) : |
42 | | """Just there to raise an exception.""" |
43 | | raise RuntimeError, "The pysnmp module is not available. Download it from http://pysnmp.sf.net/" |
44 | | else : |
45 | | pageCounterOID = ".1.3.6.1.2.1.43.10.2.1.4.1.1" # SNMPv2-SMI::mib-2.43.10.2.1.4.1.1 |
46 | | hrPrinterStatusOID = ".1.3.6.1.2.1.25.3.5.1.1.1" # SNMPv2-SMI::mib-2.25.3.5.1.1.1 |
47 | | printerStatusValues = { 1 : 'other', |
48 | | 2 : 'unknown', |
49 | | 3 : 'idle', |
50 | | 4 : 'printing', |
51 | | 5 : 'warmup', |
52 | | } |
53 | | hrDeviceStatusOID = ".1.3.6.1.2.1.25.3.2.1.5.1" # SNMPv2-SMI::mib-2.25.3.2.1.5.1 |
54 | | deviceStatusValues = { 1 : 'unknown', |
55 | | 2 : 'running', |
56 | | 3 : 'warning', |
57 | | 4 : 'testing', |
58 | | 5 : 'down', |
59 | | } |
60 | | hrPrinterDetectedErrorStateOID = ".1.3.6.1.2.1.25.3.5.1.2.1" # SNMPv2-SMI::mib-2.25.3.5.1.2.1 |
61 | | 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 |
62 | | |
63 | | # |
64 | | # Documentation taken from RFC 3805 (Printer MIB v2) and RFC 2790 (Host Resource MIB) |
65 | | # |
66 | | class Handler : |
67 | | """A class for SNMP print accounting.""" |
68 | | def __init__(self, parent, printerhostname) : |
69 | | self.parent = parent |
70 | | self.printerHostname = printerhostname |
71 | | try : |
72 | | self.community = self.parent.arguments.split(":")[1].strip() |
73 | | except IndexError : |
74 | | self.community = "public" |
75 | | self.port = 161 |
76 | | self.printerInternalPageCounter = None |
77 | | self.printerStatus = None |
78 | | self.deviceStatus = None |
79 | | |
| 35 | from pysnmp.entity.rfc3413.oneliner import cmdgen |
| 36 | except ImportError : |
| 37 | hasV4 = False |
| 38 | try : |
| 39 | from pysnmp.asn1.encoding.ber.error import TypeMismatchError |
| 40 | from pysnmp.mapping.udp.error import SnmpOverUdpError |
| 41 | from pysnmp.mapping.udp.role import Manager |
| 42 | from pysnmp.proto.api import alpha |
| 43 | except ImportError : |
| 44 | raise RuntimeError, "The pysnmp module is not available. Download it from http://pysnmp.sf.net/" |
| 45 | else : |
| 46 | hasV4 = True |
| 47 | |
| 48 | # |
| 49 | # Documentation taken from RFC 3805 (Printer MIB v2) and RFC 2790 (Host Resource MIB) |
| 50 | # |
| 51 | pageCounterOID = "1.3.6.1.2.1.43.10.2.1.4.1.1" # SNMPv2-SMI::mib-2.43.10.2.1.4.1.1 |
| 52 | hrPrinterStatusOID = "1.3.6.1.2.1.25.3.5.1.1.1" # SNMPv2-SMI::mib-2.25.3.5.1.1.1 |
| 53 | printerStatusValues = { 1 : 'other', |
| 54 | 2 : 'unknown', |
| 55 | 3 : 'idle', |
| 56 | 4 : 'printing', |
| 57 | 5 : 'warmup', |
| 58 | } |
| 59 | hrDeviceStatusOID = "1.3.6.1.2.1.25.3.2.1.5.1" # SNMPv2-SMI::mib-2.25.3.2.1.5.1 |
| 60 | deviceStatusValues = { 1 : 'unknown', |
| 61 | 2 : 'running', |
| 62 | 3 : 'warning', |
| 63 | 4 : 'testing', |
| 64 | 5 : 'down', |
| 65 | } |
| 66 | hrPrinterDetectedErrorStateOID = "1.3.6.1.2.1.25.3.5.1.2.1" # SNMPv2-SMI::mib-2.25.3.5.1.2.1 |
| 67 | 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 |
| 68 | class BaseHandler : |
| 69 | """A class for SNMP print accounting.""" |
| 70 | def __init__(self, parent, printerhostname) : |
| 71 | self.parent = parent |
| 72 | self.printerHostname = printerhostname |
| 73 | try : |
| 74 | self.community = self.parent.arguments.split(":")[1].strip() |
| 75 | except IndexError : |
| 76 | self.community = "public" |
| 77 | self.port = 161 |
| 78 | self.printerInternalPageCounter = None |
| 79 | self.printerStatus = None |
| 80 | self.deviceStatus = None |
| 81 | |
| 82 | def retrieveSNMPValues(self) : |
| 83 | """Retrieves a printer's internal page counter and status via SNMP.""" |
| 84 | raise RuntimeError, "You have to overload this method." |
| 85 | |
| 86 | def waitPrinting(self) : |
| 87 | """Waits for printer status being 'printing'.""" |
| 88 | previousValue = self.parent.getLastPageCounter() |
| 89 | timebefore = time.time() |
| 90 | firstvalue = None |
| 91 | while 1: |
| 92 | self.retrieveSNMPValues() |
| 93 | statusAsString = printerStatusValues.get(self.printerStatus) |
| 94 | if statusAsString in ('printing', 'warmup') : |
| 95 | break |
| 96 | if self.printerInternalPageCounter is not None : |
| 97 | if firstvalue is None : |
| 98 | # first time we retrieved a page counter, save it |
| 99 | firstvalue = self.printerInternalPageCounter |
| 100 | else : |
| 101 | # second time (or later) |
| 102 | if firstvalue < self.printerInternalPageCounter : |
| 103 | # Here we have a printer which lies : |
| 104 | # it says it is not printing or warming up |
| 105 | # BUT the page counter increases !!! |
| 106 | # So we can probably quit being sure it is printing. |
| 107 | self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.PrinterName, "warn") |
| 108 | break |
| 109 | elif (time.time() - timebefore) > NOPRINTINGMAXDELAY : |
| 110 | # More than X seconds without the printer being in 'printing' mode |
| 111 | # We can safely assume this won't change if printer is now 'idle' |
| 112 | pstatusAsString = printerStatusValues.get(self.printerStatus) |
| 113 | dstatusAsString = deviceStatusValues.get(self.deviceStatus) |
| 114 | if (pstatusAsString == 'idle') or \ |
| 115 | ((pstatusAsString == 'other') and \ |
| 116 | (dstatusAsString == 'running')) : |
| 117 | if self.printerInternalPageCounter == previousValue : |
| 118 | # Here the job won't be printed, because probably |
| 119 | # the printer rejected it for some reason. |
| 120 | self.parent.filter.printInfo("Printer %s probably won't print this job !!!" % self.parent.filter.PrinterName, "warn") |
| 121 | else : |
| 122 | # Here the job has already been entirely printed, and |
| 123 | # the printer has already passed from 'idle' to 'printing' to 'idle' again. |
| 124 | self.parent.filter.printInfo("Printer %s has probably already printed this job !!!" % self.parent.filter.PrinterName, "warn") |
| 125 | break |
| 126 | self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.PrinterName) |
| 127 | time.sleep(ITERATIONDELAY) |
| 128 | |
| 129 | def waitIdle(self) : |
| 130 | """Waits for printer status being 'idle'.""" |
| 131 | idle_num = idle_flag = 0 |
| 132 | while 1 : |
| 133 | self.retrieveSNMPValues() |
| 134 | pstatusAsString = printerStatusValues.get(self.printerStatus) |
| 135 | dstatusAsString = deviceStatusValues.get(self.deviceStatus) |
| 136 | idle_flag = 0 |
| 137 | if (pstatusAsString == 'idle') or \ |
| 138 | ((pstatusAsString == 'other') and \ |
| 139 | (dstatusAsString == 'running')) : |
| 140 | idle_flag = 1 # Standby / Powersave is considered idle |
| 141 | if idle_flag : |
| 142 | idle_num += 1 |
| 143 | if idle_num >= STABILIZATIONDELAY : |
| 144 | # printer status is stable, we can exit |
| 145 | break |
| 146 | else : |
| 147 | idle_num = 0 |
| 148 | self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.PrinterName) |
| 149 | time.sleep(ITERATIONDELAY) |
| 150 | |
| 151 | def retrieveInternalPageCounter(self) : |
| 152 | """Returns the page counter from the printer via internal SNMP handling.""" |
| 153 | try : |
| 154 | if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \ |
| 155 | (os.environ.get("PYKOTAACTION") == "ALLOW") and \ |
| 156 | (os.environ.get("PYKOTAPHASE") == "AFTER") and \ |
| 157 | self.parent.filter.JobSizeBytes : |
| 158 | self.waitPrinting() |
| 159 | self.waitIdle() |
| 160 | except : |
| 161 | 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") |
| 162 | raise |
| 163 | return self.printerInternalPageCounter |
| 164 | |
| 165 | if hasV4 : |
| 166 | class Handler(BaseHandler) : |
| 167 | """A class for pysnmp v4.x""" |
| 168 | def retrieveSNMPValues(self) : |
| 169 | """Retrieves a printer's internal page counter and status via SNMP.""" |
| 170 | errorIndication, errorStatus, errorIndex, varBinds = \ |
| 171 | cmdgen.CommandGenerator().getCmd(cmdgen.CommunityData("pykota", self.community, 0), \ |
| 172 | cmdgen.UdpTransportTarget((self.printerHostname, self.port)), \ |
| 173 | tuple([int(i) for i in pageCounterOID.split('.')]), \ |
| 174 | tuple([int(i) for i in hrPrinterStatusOID.split('.')]), \ |
| 175 | tuple([int(i) for i in hrDeviceStatusOID.split('.')])) |
| 176 | if errorIndication : |
| 177 | self.parent.filter.printInfo("SNMP Error : %s" % errorIndication, "error") |
| 178 | elif errorStatus : |
| 179 | self.parent.filter.printInfo("SNMP Error : %s at %s" % (errorStatus.prettyPrint(), \ |
| 180 | varBinds[int(errorIndex)-1]), \ |
| 181 | "error") |
| 182 | else : |
| 183 | self.printerInternalPageCounter = max(self.printerInternalPageCounter, int(varBinds[0][1].prettyPrint())) |
| 184 | self.printerStatus = int(varBinds[1][1].prettyPrint()) |
| 185 | self.deviceStatus = int(varBinds[2][1].prettyPrint()) |
| 186 | self.parent.filter.logdebug("SNMP answer decoded : PageCounter : %s PrinterStatus : '%s' DeviceStatus : '%s'" \ |
| 187 | % (self.printerInternalPageCounter, \ |
| 188 | printerStatusValues.get(self.printerStatus), \ |
| 189 | deviceStatusValues.get(self.deviceStatus))) |
| 190 | else : |
| 191 | class Handler(BaseHandler) : |
| 192 | """A class for pysnmp v3.4.x""" |
130 | | |
131 | | def waitPrinting(self) : |
132 | | """Waits for printer status being 'printing'.""" |
133 | | previousValue = self.parent.getLastPageCounter() |
134 | | timebefore = time.time() |
135 | | firstvalue = None |
136 | | while 1: |
137 | | self.retrieveSNMPValues() |
138 | | statusAsString = printerStatusValues.get(self.printerStatus) |
139 | | if statusAsString in ('printing', 'warmup') : |
140 | | break |
141 | | if self.printerInternalPageCounter is not None : |
142 | | if firstvalue is None : |
143 | | # first time we retrieved a page counter, save it |
144 | | firstvalue = self.printerInternalPageCounter |
145 | | else : |
146 | | # second time (or later) |
147 | | if firstvalue < self.printerInternalPageCounter : |
148 | | # Here we have a printer which lies : |
149 | | # it says it is not printing or warming up |
150 | | # BUT the page counter increases !!! |
151 | | # So we can probably quit being sure it is printing. |
152 | | self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.PrinterName, "warn") |
153 | | break |
154 | | elif (time.time() - timebefore) > NOPRINTINGMAXDELAY : |
155 | | # More than X seconds without the printer being in 'printing' mode |
156 | | # We can safely assume this won't change if printer is now 'idle' |
157 | | pstatusAsString = printerStatusValues.get(self.printerStatus) |
158 | | dstatusAsString = deviceStatusValues.get(self.deviceStatus) |
159 | | if (pstatusAsString == 'idle') or \ |
160 | | ((pstatusAsString == 'other') and \ |
161 | | (dstatusAsString == 'running')) : |
162 | | if self.printerInternalPageCounter == previousValue : |
163 | | # Here the job won't be printed, because probably |
164 | | # the printer rejected it for some reason. |
165 | | self.parent.filter.printInfo("Printer %s probably won't print this job !!!" % self.parent.filter.PrinterName, "warn") |
166 | | else : |
167 | | # Here the job has already been entirely printed, and |
168 | | # the printer has already passed from 'idle' to 'printing' to 'idle' again. |
169 | | self.parent.filter.printInfo("Printer %s has probably already printed this job !!!" % self.parent.filter.PrinterName, "warn") |
170 | | break |
171 | | self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.PrinterName) |
172 | | time.sleep(ITERATIONDELAY) |
173 | | |
174 | | def waitIdle(self) : |
175 | | """Waits for printer status being 'idle'.""" |
176 | | idle_num = idle_flag = 0 |
177 | | while 1 : |
178 | | self.retrieveSNMPValues() |
179 | | pstatusAsString = printerStatusValues.get(self.printerStatus) |
180 | | dstatusAsString = deviceStatusValues.get(self.deviceStatus) |
181 | | idle_flag = 0 |
182 | | if (pstatusAsString == 'idle') or \ |
183 | | ((pstatusAsString == 'other') and \ |
184 | | (dstatusAsString == 'running')) : |
185 | | idle_flag = 1 # Standby / Powersave is considered idle |
186 | | if idle_flag : |
187 | | idle_num += 1 |
188 | | if idle_num >= STABILIZATIONDELAY : |
189 | | # printer status is stable, we can exit |
190 | | break |
191 | | else : |
192 | | idle_num = 0 |
193 | | self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.PrinterName) |
194 | | time.sleep(ITERATIONDELAY) |
195 | | |
196 | | def retrieveInternalPageCounter(self) : |
197 | | """Returns the page counter from the printer via internal SNMP handling.""" |
198 | | try : |
199 | | if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \ |
200 | | (os.environ.get("PYKOTAACTION") == "ALLOW") and \ |
201 | | (os.environ.get("PYKOTAPHASE") == "AFTER") and \ |
202 | | self.parent.filter.JobSizeBytes : |
203 | | self.waitPrinting() |
204 | | self.waitIdle() |
205 | | except : |
206 | | 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") |
207 | | raise |
208 | | return self.printerInternalPageCounter |
209 | | |
| 243 | |