- Timestamp:
- 11/25/08 00:51:30 (16 years ago)
- Files:
-
- 1 modified
Legend:
- Unmodified
- Added
- Removed
-
pykota/branches/1.26_fixes/bin/cupspykota
r3189 r3459 1 1 #! /usr/bin/env python 2 # -*- coding: ISO-8859-15 -*-2 # -*- coding: iso-8859-15 -*- 3 3 4 4 # CUPSPyKota accounting backend … … 16 16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 17 # GNU General Public License for more details. 18 # 18 # 19 19 # You should have received a copy of the GNU General Public License 20 20 # along with this program; if not, write to the Free Software … … 55 55 try : 56 56 from pkipplib import pkipplib 57 except ImportError : 57 except ImportError : 58 58 haspkipplib = False 59 else : 59 else : 60 60 haspkipplib = True 61 62 class FakeObject : 61 62 class FakeObject : 63 63 """Fake object.""" 64 64 def __init__(self, name) : 65 65 """Fake init.""" 66 66 self.Name = name 67 68 class FakePrinter(FakeObject) : 67 68 class FakePrinter(FakeObject) : 69 69 """Fake printer instance.""" 70 70 pass 71 72 class FakeUser(FakeObject) : 71 72 class FakeUser(FakeObject) : 73 73 """Fake user instance.""" 74 74 def __init__(self, name) : … … 76 76 self.Email = name 77 77 FakeObject.__init__(self, name) 78 78 79 79 class CUPSBackend(PyKotaTool) : 80 80 """Base class for tools with no database access.""" … … 90 90 self.lockfilename = None 91 91 self.lockfile = None 92 93 def deferredInit(self) : 92 93 def deferredInit(self) : 94 94 """Deferred initialization.""" 95 95 PyKotaTool.deferredInit(self) … … 101 101 self.disableSigInt() 102 102 self.installSigTermHandler() 103 103 104 104 def sigtermHandler(self, signum, frame) : 105 105 """Sets an attribute whenever SIGTERM is received.""" … … 107 107 self.printInfo(_("SIGTERM received, job %s cancelled.") % self.JobId) 108 108 os.environ["PYKOTASTATUS"] = "CANCELLED" 109 110 def deinstallSigTermHandler(self) : 109 110 def deinstallSigTermHandler(self) : 111 111 """Deinstalls the SIGTERM handler.""" 112 112 self.logdebug("Deinstalling SIGTERM handler...") 113 113 signal.signal(signal.SIGTERM, signal.SIG_IGN) 114 114 self.logdebug("SIGTERM handler deinstalled.") 115 116 def installSigTermHandler(self) : 115 116 def installSigTermHandler(self) : 117 117 """Installs the SIGTERM handler.""" 118 118 self.logdebug("Installing SIGTERM handler...") 119 119 signal.signal(signal.SIGTERM, self.sigtermHandler) 120 120 self.logdebug("SIGTERM handler installed.") 121 122 def disableSigInt(self) : 121 122 def disableSigInt(self) : 123 123 """Disables the SIGINT signal (which raises KeyboardInterrupt).""" 124 124 self.logdebug("Disabling SIGINT...") 125 125 self.oldSigIntHandler = signal.signal(signal.SIGINT, signal.SIG_IGN) 126 126 self.logdebug("SIGINT disabled.") 127 128 def enableSigInt(self) : 127 128 def enableSigInt(self) : 129 129 """Enables the SIGINT signal (which raises KeyboardInterrupt).""" 130 130 self.logdebug("Enabling SIGINT...") 131 131 signal.signal(signal.SIGINT, self.oldSigIntHandler) 132 132 self.logdebug("SIGINT enabled.") 133 134 def waitForLock(self) : 133 134 def waitForLock(self) : 135 135 """Waits until we can acquire the lock file.""" 136 136 self.logdebug("Waiting for lock %s to become available..." % self.lockfilename) … … 140 140 # open the lock file, optionally creating it if needed. 141 141 self.lockfile = open(self.lockfilename, "a+") 142 142 143 143 # we wait indefinitely for the lock to become available. 144 144 # works over NFS too. 145 145 fcntl.lockf(self.lockfile, fcntl.LOCK_EX) 146 146 haslock = True 147 147 148 148 self.logdebug("Lock %s acquired." % self.lockfilename) 149 149 150 150 # Here we save the PID in the lock file, but we don't use 151 151 # it, because the lock file may be in a directory shared … … 156 156 self.lockfile.write(str(self.pid)) 157 157 self.lockfile.flush() 158 except IOError, msg : 158 except IOError, msg : 159 159 self.logdebug("I/O Error while waiting for lock %s : %s" % (self.lockfilename, msg)) 160 160 time.sleep(0.25) 161 162 def discoverOtherBackends(self) : 161 162 def discoverOtherBackends(self) : 163 163 """Discovers the other CUPS backends. 164 164 165 165 Executes each existing backend in turn in device enumeration mode. 166 166 Returns the list of available backends. … … 186 186 # process doesn't exist anymore 187 187 os.remove(lockfilename) 188 188 189 189 if not os.path.exists(lockfilename) : 190 190 lockfile = open(lockfilename, "w") … … 194 194 for b in os.listdir(directory) \ 195 195 if os.access(os.path.join(directory, b), os.X_OK) \ 196 and (b != myname)] 197 for backend in allbackends : 196 and (b != myname)] 197 for backend in allbackends : 198 198 answer = os.popen(backend, "r") 199 199 try : 200 200 devices = [line.strip() for line in answer.readlines()] 201 except : 201 except : 202 202 devices = [] 203 203 status = answer.close() 204 204 if status is None : 205 205 for d in devices : 206 # each line is of the form : 206 # each line is of the form : 207 207 # 'xxxx xxxx "xxxx xxx" "xxxx xxx"' 208 208 # so we have to decompose it carefully … … 221 221 try : 222 222 (devicetype, device, name, fullname) = arguments 223 except ValueError : 223 except ValueError : 224 224 pass # ignore this 'bizarre' device 225 else : 225 else : 226 226 if name.startswith('"') and name.endswith('"') : 227 227 name = name[1:-1] … … 237 237 % (self.myname, self.MyName, self.MyName)) 238 238 return available 239 240 def initBackendParameters(self) : 239 240 def initBackendParameters(self) : 241 241 """Initializes the backend's attributes.""" 242 # check that the DEVICE_URI environment variable's value is 242 # check that the DEVICE_URI environment variable's value is 243 243 # prefixed with self.myname otherwise don't touch it. 244 # If this is the case, we have to remove the prefix from 245 # the environment before launching the real backend 244 # If this is the case, we have to remove the prefix from 245 # the environment before launching the real backend 246 246 self.logdebug("Initializing backend...") 247 247 self.softwareJobSize = None 248 248 self.PrinterName = os.environ.get("PRINTER", "") 249 249 directories = [ self.config.getPrinterDirectory(self.PrinterName), … … 257 257 else : 258 258 self.printInfo("Insufficient permissions to access to temporary directory %s" % direc, "warn") 259 259 260 260 self.Action = "ALLOW" # job allowed by default 261 261 self.Reason = None … … 269 269 if len(sys.argv) == 7 : 270 270 self.InputFile = sys.argv[6] # read job's datas from file 271 else : 271 else : 272 272 self.InputFile = None # read job's datas from stdin 273 273 self.DataFile = os.path.join(self.Directory, "%s-%s-%s-%s" % \ 274 274 (self.myname, self.PrinterName, self.UserName, self.JobId)) 275 275 276 276 muststartwith = "%s:" % self.myname 277 277 device_uri = os.environ.get("DEVICE_URI", "") … … 280 280 device_uri = fulldevice_uri[len(muststartwith):] 281 281 for i in range(2) : 282 if device_uri.startswith("/") : 282 if device_uri.startswith("/") : 283 283 device_uri = device_uri[1:] 284 284 try : 285 (backend, destination) = device_uri.split(":", 1) 286 except ValueError : 285 (backend, destination) = device_uri.split(":", 1) 286 except ValueError : 287 287 if not device_uri : 288 288 self.logdebug("Not attached to an existing print queue.") 289 289 backend = "" 290 290 printerhostname = "" 291 else : 291 else : 292 292 raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri 293 else : 293 else : 294 294 if backend == "hp" : 295 295 try : 296 296 printerhostname = destination.split("=")[1] # hp:/net/HP_LaserJet_8000_Series?ip=192.168.100.100 297 except IndexError : 297 except IndexError : 298 298 self.logdebug("Unsupported hplip URI %s" % device_uri) 299 printerhostname = "" 300 else : 299 printerhostname = "" 300 else : 301 301 while destination.startswith("/") : 302 302 destination = destination[1:] 303 checkauth = destination.split("@", 1) 303 checkauth = destination.split("@", 1) 304 304 if len(checkauth) == 2 : 305 305 destination = checkauth[1] 306 306 printerhostname = destination.split("/")[0].split(":")[0] 307 308 self.PrinterHostName = printerhostname 307 308 self.PrinterHostName = printerhostname 309 309 self.RealBackend = backend 310 310 self.DeviceURI = device_uri 311 311 312 312 connerror = False 313 313 if haspkipplib : … … 319 319 self.printInfo(_("Network error while querying the CUPS server : %s") \ 320 320 % cupsserver.lastErrorMessage, "error") 321 connerror = True 322 else : 321 connerror = True 322 else : 323 323 self.logdebug("CUPS server answered without error.") 324 324 try : 325 325 john = answer.job["job-originating-host-name"] 326 except KeyError : 326 except KeyError : 327 327 try : 328 328 john = answer.operation["job-originating-host-name"] 329 except KeyError : 329 except KeyError : 330 330 john = (None, None) 331 try : 331 try : 332 332 jbing = answer.job["job-billing"] 333 except KeyError : 333 except KeyError : 334 334 jbing = (None, None) 335 336 if connerror or not haspkipplib : 335 336 if connerror or not haspkipplib : 337 337 (ippfilename, ippmessage) = self.parseIPPRequestFile() 338 338 self.ControlFile = ippfilename … … 341 341 (None, None))) 342 342 jbing = ippmessage.job_attributes.get("job-billing", (None, None)) 343 344 if type(john) == type([]) : 343 344 if type(john) == type([]) : 345 345 john = john[-1] 346 (chtype, self.ClientHost) = john 347 if type(jbing) == type([]) : 346 (chtype, self.ClientHost) = john 347 if type(jbing) == type([]) : 348 348 jbing = jbing[-1] 349 349 (jbtype, self.JobBillingCode) = jbing 350 350 if self.JobBillingCode is None : 351 351 self.OriginalJobBillingCode = None 352 else : 352 else : 353 353 self.JobBillingCode = self.UTF8ToUserCharset(self.JobBillingCode) 354 354 self.OriginalJobBillingCode = self.JobBillingCode[:] 355 355 356 356 baselockfilename = self.DeviceURI.replace("/", ".") 357 357 baselockfilename = baselockfilename.replace(":", ".") … … 360 360 baselockfilename = baselockfilename.replace("@", ".") 361 361 self.lockfilename = os.path.join(self.Directory, "%s-%s..LCK" % (self.myname, baselockfilename)) 362 362 363 363 self.logdebug("Backend : %s" % self.RealBackend) 364 364 self.logdebug("DeviceURI : %s" % self.DeviceURI) … … 370 370 self.logdebug("Copies : %s" % self.Copies) 371 371 self.logdebug("Options : %s" % self.Options) 372 self.logdebug("Directory : %s" % self.Directory) 372 self.logdebug("Directory : %s" % self.Directory) 373 373 self.logdebug("DataFile : %s" % self.DataFile) 374 374 self.logdebug("ControlFile : %s" % self.ControlFile) 375 375 self.logdebug("JobBillingCode : %s" % self.JobBillingCode) 376 376 self.logdebug("JobOriginatingHostName : %s" % self.ClientHost) 377 377 378 378 # fakes some entries to allow for external mailto 379 379 # before real entries are extracted from the database. 380 380 self.User = FakeUser(self.UserName) 381 381 self.Printer = FakePrinter(self.PrinterName) 382 382 383 383 self.enableSigInt() 384 384 self.logdebug("Backend initialized.") 385 385 386 386 def overwriteJobAttributes(self) : 387 387 """Overwrites some of the job's attributes if needed.""" … … 389 389 # First overwrite the job ticket 390 390 self.overwriteJobTicket() 391 391 392 392 # do we want to strip out the Samba/Winbind domain name ? 393 393 separator = self.config.getWinbindSeparator() 394 394 if separator is not None : 395 395 self.UserName = self.UserName.split(separator)[-1] 396 397 # this option is deprecated, and we want to tell people 396 397 # this option is deprecated, and we want to tell people 398 398 # this is the case. 399 399 tolower = self.config.getUserNameToLower() … … 404 404 if self.config.isTrue(tolower) : 405 405 self.UserName = self.UserName.lower() 406 407 # Now use the newer and more complete 'usernamecase' directive. 408 casechange = self.config.getUserNameCase() 406 407 # Now use the newer and more complete 'usernamecase' directive. 408 casechange = self.config.getUserNameCase() 409 409 if casechange != "native" : 410 410 self.UserName = getattr(self.UserName, casechange)() 411 412 # do we want to strip some prefix off of titles ? 411 412 # do we want to strip some prefix off of titles ? 413 413 stripprefix = self.config.getStripTitle(self.PrinterName) 414 414 if stripprefix : … … 417 417 % (stripprefix, self.Title)) 418 418 self.Title = self.Title[len(stripprefix):] 419 419 420 420 self.logdebug("Username : %s" % self.UserName) 421 421 self.logdebug("BillingCode : %s" % self.JobBillingCode) 422 422 self.logdebug("Title : %s" % self.Title) 423 423 self.logdebug("Job's attributes sanitizing done.") 424 424 425 425 def didUserConfirm(self) : 426 426 """Asks for user confirmation through an external script. 427 427 428 428 returns False if the end user wants to cancel the job, else True. 429 429 """ 430 430 self.logdebug("Checking if we have to ask for user's confirmation...") 431 answer = None 431 answer = None 432 432 confirmationcommand = self.config.getAskConfirmation(self.PrinterName) 433 433 if confirmationcommand : … … 440 440 if answer == "CANCEL" : 441 441 break 442 except IOError, msg : 442 except IOError, msg : 443 443 self.logdebug("IOError while reading subprocess' output : %s" % msg) 444 inputfile.close() 444 inputfile.close() 445 445 self.logdebug("User's confirmation received : %s" % (((answer == "CANCEL") and "CANCEL") or "CONTINUE")) 446 else : 446 else : 447 447 self.logdebug("No need to ask for user's confirmation, job processing will continue.") 448 return (answer != "CANCEL") 449 450 def overwriteJobTicket(self) : 448 return (answer != "CANCEL") 449 450 def overwriteJobTicket(self) : 451 451 """Should we overwrite the job's ticket (username and billingcode) ?""" 452 452 self.logdebug("Checking if we need to overwrite the job ticket...") … … 456 456 self.logdebug("Launching subprocess [%s] to overwrite the job ticket." \ 457 457 % jobticketcommand) 458 self.regainPriv() 458 self.regainPriv() 459 459 inputfile = os.popen(jobticketcommand, "r") 460 460 try : … … 467 467 self.logdebug("Seen CANCEL command.") 468 468 action = "CANCEL" 469 elif line.startswith("USERNAME=") : 469 elif line.startswith("USERNAME=") : 470 470 username = self.userCharsetToUTF8(line.split("=", 1)[1].strip()) 471 471 self.logdebug("Seen new username [%s]" % username) 472 elif line.startswith("BILLINGCODE=") : 472 elif line.startswith("BILLINGCODE=") : 473 473 billingcode = self.userCharsetToUTF8(line.split("=", 1)[1].strip()) 474 474 self.logdebug("Seen new billing code [%s]" % billingcode) … … 476 476 reason = self.userCharsetToUTF8(line.split("=", 1)[1].strip()) 477 477 self.logdebug("Seen new reason [%s]" % reason) 478 except IOError, msg : 478 except IOError, msg : 479 479 self.logdebug("IOError while reading subprocess' output : %s" % msg) 480 inputfile.close() 480 inputfile.close() 481 481 self.dropPriv() 482 482 483 483 # now overwrite the job's ticket if new data was supplied 484 484 if action == "DENY" : … … 495 495 self.UserName = username 496 496 if billingcode is not None : 497 self.JobBillingCode = billingcode 497 self.JobBillingCode = billingcode 498 498 self.logdebug("Job ticket overwriting done.") 499 499 500 500 def saveDatasAndCheckSum(self) : 501 501 """Saves the input datas into a static file.""" 502 502 self.logdebug("Duplicating data stream into %s" % self.DataFile) 503 503 mustclose = 0 504 outfile = open(self.DataFile, "wb") 504 outfile = open(self.DataFile, "wb") 505 505 if self.InputFile is not None : 506 506 self.regainPriv() … … 508 508 self.logdebug("Reading input datas from %s" % self.InputFile) 509 509 mustclose = 1 510 else : 510 else : 511 511 infile = sys.stdin 512 512 self.logdebug("Reading input datas from stdin") … … 516 516 checksum = md5.new() 517 517 while 1 : 518 data = infile.read(CHUNK) 518 data = infile.read(CHUNK) 519 519 if not data : 520 520 break 521 sizeread += len(data) 521 sizeread += len(data) 522 522 outfile.write(data) 523 checksum.update(data) 523 checksum.update(data) 524 524 if not (dummy % 32) : # Only display every 2 Mb 525 525 self.logdebug("%s bytes saved..." % sizeread) 526 dummy += 1 527 if mustclose : 526 dummy += 1 527 if mustclose : 528 528 infile.close() 529 529 self.dropPriv() 530 530 531 531 outfile.close() 532 self.JobSizeBytes = sizeread 532 self.JobSizeBytes = sizeread 533 533 self.JobMD5Sum = checksum.hexdigest() 534 534 535 535 self.logdebug("JobSizeBytes : %s" % self.JobSizeBytes) 536 536 self.logdebug("JobMD5Sum : %s" % self.JobMD5Sum) 537 537 self.logdebug("Data stream duplicated into %s" % self.DataFile) 538 538 539 539 def clean(self) : 540 540 """Cleans up the place.""" … … 545 545 try : 546 546 keep = self.config.getPrinterKeepFiles(self.PrinterName) 547 except AttributeError : 547 except AttributeError : 548 548 keep = False 549 549 if not keep : … … 555 555 else : 556 556 self.logdebug("Work file %s has been deleted." % self.DataFile) 557 else : 557 else : 558 558 self.logdebug("Work file %s will be kept." % self.DataFile) 559 PyKotaTool.clean(self) 559 PyKotaTool.clean(self) 560 560 if self.lockfile is not None : 561 561 self.logdebug("Unlocking %s..." % self.lockfilename) … … 563 563 fcntl.lockf(self.lockfile, fcntl.LOCK_UN) 564 564 self.lockfile.close() 565 except : 565 except : 566 566 self.printInfo("Problem while unlocking %s" % self.lockfilename, "error") 567 else : 567 else : 568 568 self.logdebug("%s unlocked." % self.lockfilename) 569 569 self.logdebug("Clean.") 570 571 def precomputeJobSize(self) : 570 571 def precomputeJobSize(self) : 572 572 """Computes the job size with a software method.""" 573 573 self.logdebug("Precomputing job's size...") … … 576 576 self.softwareJobSize = self.preaccounter.getJobSize(None) 577 577 self.logdebug("Precomputed job's size is %s pages." % self.softwareJobSize) 578 579 def precomputeJobPrice(self) : 578 579 def precomputeJobPrice(self) : 580 580 """Precomputes the job price with a software method.""" 581 581 self.logdebug("Precomputing job's price...") … … 583 583 self.logdebug("Precomputed job's price is %.3f credits." \ 584 584 % self.softwareJobPrice) 585 585 586 586 def getCupsConfigDirectives(self, directives=[]) : 587 587 """Retrieves some CUPS directives from its configuration file. 588 589 Returns a mapping with lowercased directives as keys and 588 589 Returns a mapping with lowercased directives as keys and 590 590 their setting as values. 591 591 """ 592 592 self.logdebug("Parsing CUPS' configuration file...") 593 dirvalues = {} 593 dirvalues = {} 594 594 cupsroot = os.environ.get("CUPS_SERVERROOT", "/etc/cups") 595 595 cupsdconf = os.path.join(cupsroot, "cupsd.conf") 596 596 try : 597 597 conffile = open(cupsdconf, "r") 598 except IOError : 598 except IOError : 599 599 raise PyKotaToolError, "Unable to open %s" % cupsdconf 600 else : 600 else : 601 601 for line in conffile.readlines() : 602 602 linecopy = line.strip().lower() … … 605 605 try : 606 606 val = line.split()[1] 607 except : 607 except : 608 608 pass # ignore errors, we take the last value in any case. 609 else : 609 else : 610 610 dirvalues[di] = val 611 conffile.close() 611 conffile.close() 612 612 self.logdebug("CUPS' configuration file parsed successfully.") 613 return dirvalues 614 615 def parseIPPRequestFile(self) : 613 return dirvalues 614 615 def parseIPPRequestFile(self) : 616 616 """Parses the IPP message file and returns a tuple (filename, parsedvalue).""" 617 617 self.logdebug("Parsing IPP request file...") 618 618 619 619 class DummyClass : 620 620 """Class used to avoid errors.""" 621 621 operation_attributes = {} 622 622 job_attributes = {} 623 623 624 624 ippmessage = DummyClass() # in case the code below fails 625 625 626 626 self.regainPriv() 627 627 cupsdconf = self.getCupsConfigDirectives(["RequestRoot"]) … … 629 629 if (len(self.JobId) < 5) and self.JobId.isdigit() : 630 630 ippmessagefile = "c%05i" % int(self.JobId) 631 else : 631 else : 632 632 ippmessagefile = "c%s" % self.JobId 633 633 ippmessagefile = os.path.join(requestroot, ippmessagefile) 634 634 try : 635 635 ippdatafile = open(ippmessagefile) 636 except : 636 except : 637 637 self.logdebug("Unable to open IPP request file %s" % ippmessagefile) 638 else : 638 else : 639 639 self.logdebug("Parsing of IPP request file %s begins." % ippmessagefile) 640 640 try : 641 641 ippmessage = oldIPPRequest(ippdatafile.read()) 642 642 ippmessage.parse() 643 except oldIPPError, msg : 643 except oldIPPError, msg : 644 644 self.printInfo("Error while parsing %s : %s" \ 645 645 % (ippmessagefile, msg), "warn") 646 else : 646 else : 647 647 self.logdebug("Parsing of IPP request file %s ends." \ 648 648 % ippmessagefile) … … 651 651 self.logdebug("IPP request file parsed successfully.") 652 652 return (ippmessagefile, ippmessage) 653 654 def exportJobInfo(self) : 653 654 def exportJobInfo(self) : 655 655 """Exports the actual job's attributes to the environment.""" 656 656 self.logdebug("Exporting job information to the environment...") … … 675 675 os.environ["PYKOTAPRECOMPUTEDJOBSIZE"] = str(self.softwareJobSize) 676 676 self.logdebug("Environment updated.") 677 677 678 678 def exportUserInfo(self) : 679 679 """Exports user information to the environment.""" … … 684 684 os.environ["PYKOTALIFETIMEPAID"] = str(self.User.LifeTimePaid or 0.0) 685 685 os.environ["PYKOTAUSERDESCRIPTION"] = str(self.User.Description or "") 686 686 687 687 os.environ["PYKOTAPAGECOUNTER"] = str(self.UserPQuota.PageCounter or 0) 688 688 os.environ["PYKOTALIFEPAGECOUNTER"] = str(self.UserPQuota.LifePageCounter or 0) … … 691 691 os.environ["PYKOTADATELIMIT"] = str(self.UserPQuota.DateLimit) 692 692 os.environ["PYKOTAWARNCOUNT"] = str(self.UserPQuota.WarnCount) 693 693 694 694 # TODO : move this elsewhere once software accounting is done only once. 695 695 os.environ["PYKOTAPRECOMPUTEDJOBPRICE"] = str(self.softwareJobPrice) 696 696 697 697 self.logdebug("Environment updated.") 698 698 699 699 def exportPrinterInfo(self) : 700 700 """Exports printer information to the environment.""" … … 709 709 os.environ["PYKOTAPRICEPERJOB"] = str(self.Printer.PricePerJob or 0) 710 710 self.logdebug("Environment updated.") 711 711 712 712 def exportPhaseInfo(self, phase) : 713 713 """Exports phase information to the environment.""" … … 715 715 os.environ["PYKOTAPHASE"] = phase 716 716 self.logdebug("Environment updated.") 717 717 718 718 def exportJobSizeAndPrice(self) : 719 719 """Exports job's size and price information to the environment.""" … … 722 722 os.environ["PYKOTAJOBPRICE"] = str(self.JobPrice) 723 723 self.logdebug("Environment updated.") 724 724 725 725 def exportReason(self) : 726 726 """Exports the job's action status and optional reason.""" … … 730 730 os.environ["PYKOTAREASON"] = str(self.Reason) 731 731 self.logdebug("Environment updated.") 732 733 def acceptJob(self) : 732 733 def acceptJob(self) : 734 734 """Returns the appropriate exit code to tell CUPS all is OK.""" 735 735 return 0 736 737 def removeJob(self) : 736 737 def removeJob(self) : 738 738 """Returns the appropriate exit code to let CUPS think all is OK. 739 739 740 740 Returning 0 (success) prevents CUPS from stopping the print queue. 741 """ 741 """ 742 742 return 0 743 743 744 744 def launchPreHook(self) : 745 745 """Allows plugging of an external hook before the job gets printed.""" … … 749 749 retcode = os.system(prehook) 750 750 self.logdebug("pre-hook exited with status %s." % retcode) 751 751 752 752 def launchPostHook(self) : 753 753 """Allows plugging of an external hook after the job gets printed and/or denied.""" … … 757 757 retcode = os.system(posthook) 758 758 self.logdebug("post-hook exited with status %s." % retcode) 759 760 def improveMessage(self, message) : 759 760 def improveMessage(self, message) : 761 761 """Improves a message by adding more informations in it if possible.""" 762 762 try : … … 765 765 self.JobId, \ 766 766 message) 767 except : 767 except : 768 768 return message 769 770 def logdebug(self, message) : 769 770 def logdebug(self, message) : 771 771 """Improves the debug message before outputting it.""" 772 772 PyKotaTool.logdebug(self, self.improveMessage(message)) 773 774 def printInfo(self, message, level="info") : 773 774 def printInfo(self, message, level="info") : 775 775 """Improves the informational message before outputting it.""" 776 776 self.logger.log_message(self.improveMessage(message), level) 777 777 778 778 def startingBanner(self, withaccounting) : 779 779 """Retrieves a starting banner for current printer and returns its content.""" … … 781 781 self.printBanner(self.config.getStartingBanner(self.PrinterName), withaccounting) 782 782 self.logdebug("Starting banner retrieved.") 783 783 784 784 def endingBanner(self, withaccounting) : 785 785 """Retrieves an ending banner for current printer and returns its content.""" … … 787 787 self.printBanner(self.config.getEndingBanner(self.PrinterName), withaccounting) 788 788 self.logdebug("Ending banner retrieved.") 789 789 790 790 def printBanner(self, bannerfileorcommand, withaccounting) : 791 791 """Reads a banner or generates one through an external command. 792 792 793 793 Returns the banner's content in a format which MUST be accepted 794 794 by the printer. … … 816 816 try : 817 817 fh = open(bannerfileorcommand, 'rb') 818 except IOError, msg : 818 except IOError, msg : 819 819 self.printInfo("Impossible to open %s : %s" \ 820 820 % (bannerfileorcommand, msg), "error") 821 else : 821 else : 822 822 self.runOriginalBackend(fh, isBanner=1) 823 823 fh.close() … … 826 826 self.BannerSize += 1 # TODO : fix this by passing the banner's content through software accounting 827 827 self.logdebug("Banner printed...") 828 828 829 829 def handleBanner(self, bannertype, withaccounting) : 830 830 """Handles the banner with or without accounting.""" 831 831 if withaccounting : 832 832 acc = "with" 833 else : 833 else : 834 834 acc = "without" 835 835 self.logdebug("Handling %s banner %s accounting..." % (bannertype, acc)) … … 860 860 if (avoidduplicatebanners == "YES") : 861 861 printbanner = False 862 else : 863 # avoidduplicatebanners is an integer, since NO, 862 else : 863 # avoidduplicatebanners is an integer, since NO, 864 864 # YES and 0 are already handled 865 865 now = DateTime.now() … … 871 871 self.logdebug("Difference with previous job : %.2f seconds. Try to avoid banners for : %.2f seconds." % (difference, avoidduplicatebanners)) 872 872 if difference < avoidduplicatebanners : 873 self.logdebug("Duplicate banner avoided because previous banner is less than %.2f seconds old." % avoidduplicatebanners) 873 self.logdebug("Duplicate banner avoided because previous banner is less than %.2f seconds old." % avoidduplicatebanners) 874 874 printbanner = False 875 875 else : … … 878 878 getattr(self, "%sBanner" % bannertype)(withaccounting) 879 879 self.logdebug("%s banner done." % bannertype.title()) 880 881 def sanitizeJobSize(self) : 880 881 def sanitizeJobSize(self) : 882 882 """Sanitizes the job's size if needed.""" 883 883 # TODO : there's a difficult to see bug here when banner accounting is activated and hardware accounting is used. … … 897 897 if replacement == "PRECOMPUTED" : 898 898 self.JobSize = self.softwareJobSize 899 else : 899 else : 900 900 self.JobSize = replacement 901 901 self.logdebug("Job's size sanitized.") 902 903 def getPrinterUserAndUserPQuota(self) : 902 903 def getPrinterUserAndUserPQuota(self) : 904 904 """Returns a tuple (policy, printer, user, and user print quota) on this printer. 905 905 906 906 "OK" is returned in the policy if both printer, user and user print quota 907 907 exist in the Quota Storage. 908 908 Otherwise, the policy as defined for this printer in pykota.conf is returned. 909 909 910 910 If policy was set to "EXTERNAL" and one of printer, user, or user print quota 911 911 doesn't exist in the Quota Storage, then an external command is launched, as … … 914 914 or users, for example, and finally extracting printer, user and user print 915 915 quota from the Quota Storage is tried a second time. 916 916 917 917 "EXTERNALERROR" is returned in case policy was "EXTERNAL" and an error status 918 918 was returned by the external command. … … 927 927 break 928 928 (policy, args) = self.config.getPrinterPolicy(self.PrinterName) 929 if policy == "EXTERNAL" : 929 if policy == "EXTERNAL" : 930 930 commandline = self.formatCommandLine(args, user, printer) 931 931 if not printer.Exists : … … 939 939 policy = "EXTERNALERROR" 940 940 break 941 else : 941 else : 942 942 if not printer.Exists : 943 943 self.printInfo(_("Printer %s not registered in the PyKota system, applying default policy (%s)") % (self.PrinterName, policy)) … … 947 947 self.printInfo(_("User %s doesn't have quota on printer %s in the PyKota system, applying default policy (%s)") % (self.UserName, self.PrinterName, policy)) 948 948 break 949 950 if policy == "EXTERNAL" : 949 950 if policy == "EXTERNAL" : 951 951 if not printer.Exists : 952 952 self.printInfo(_("Printer %s still not registered in the PyKota system, job will be rejected") % self.PrinterName) … … 955 955 if not userpquota.Exists : 956 956 self.printInfo(_("User %s still doesn't have quota on printer %s in the PyKota system, job will be rejected") % (self.UserName, self.PrinterName)) 957 self.Policy = policy 957 self.Policy = policy 958 958 self.Printer = printer 959 959 self.User = user 960 960 self.UserPQuota = userpquota 961 961 self.logdebug("Retrieval of printer, user and user print quota entry done.") 962 963 def getBillingCode(self) : 962 963 def getBillingCode(self) : 964 964 """Extracts the billing code from the database. 965 965 966 966 An optional script is launched to notify the user when 967 967 the billing code is unknown and PyKota was configured to … … 983 983 if self.BillingCode.Exists : 984 984 self.logdebug(msg + "has been created.") 985 else : 985 else : 986 986 self.printInfo(msg + "couldn't be created.", "error") 987 else : 987 else : 988 988 self.logdebug(msg + "job will be denied.") 989 989 self.Action = newaction 990 if script is not None : 990 if script is not None : 991 991 self.logdebug(msg + "launching subprocess [%s] to notify user." % script) 992 992 os.system(script) 993 993 self.logdebug("Retrieval of billing code information done.") 994 995 def checkIfDupe(self) : 994 995 def checkIfDupe(self) : 996 996 """Checks if the job is a duplicate, and handles the situation.""" 997 997 self.logdebug("Checking if the job is a duplicate...") … … 1014 1014 self.logdebug("Duplicate job allowed because previous one is more than %.2f seconds old." % duplicatesdelay) 1015 1015 else : 1016 # TODO : use the current user's last job instead of 1016 # TODO : use the current user's last job instead of 1017 1017 # TODO : the current printer's last job. This would be 1018 1018 # TODO : better but requires an additional database query 1019 # TODO : with SQL, and is much more complex with the 1019 # TODO : with SQL, and is much more complex with the 1020 1020 # TODO : actual LDAP schema. Maybe this is not very 1021 1021 # TODO : important, because usually duplicate jobs are sucessive. … … 1025 1025 self.Action = "DENY" 1026 1026 self.Reason = _("Duplicate print jobs are not allowed on printer %s.") % self.PrinterName 1027 else : 1027 else : 1028 1028 self.logdebug("Launching subprocess [%s] to see if duplicate jobs should be allowed or not." % denyduplicates) 1029 1029 fanswer = os.popen(denyduplicates, "r") 1030 1030 self.Action = fanswer.read().strip().upper() 1031 1031 fanswer.close() 1032 if self.Action == "DENY" : 1032 if self.Action == "DENY" : 1033 1033 self.printInfo("%s : %s." % (msg, _("Subprocess denied printing of a dupe")), "warn") 1034 1034 self.Reason = _("Duplicate print jobs are not allowed on printer %s at this time.") % self.PrinterName 1035 else : 1035 else : 1036 1036 self.printInfo("%s : %s." % (msg, _("Subprocess allowed printing of a dupe")), "warn") 1037 else : 1037 else : 1038 1038 self.logdebug("Job doesn't seem to be a duplicate.") 1039 1039 self.logdebug("Checking if the job is a duplicate done.") 1040 1040 1041 1041 def tellUser(self) : 1042 1042 """Sends a message to an user.""" 1043 self.logdebug("Sending some feedback to user %s..." % self.UserName) 1043 self.logdebug("Sending some feedback to user %s..." % self.UserName) 1044 1044 if not self.Reason : 1045 1045 self.logdebug("No feedback to send to user %s." % self.UserName) 1046 else : 1046 else : 1047 1047 (mailto, arguments) = self.config.getMailTo(self.PrinterName) 1048 1048 if mailto == "EXTERNAL" : … … 1051 1051 self.externalMailTo(arguments, self.Action, self.User, self.Printer, self.Reason) 1052 1052 self.dropPriv() 1053 else : 1053 else : 1054 1054 # TODO : clean this again 1055 1055 admin = self.config.getAdmin(self.PrinterName) … … 1061 1061 if mailto in ("BOTH", "ADMIN") : 1062 1062 destination.append(adminmail) 1063 if mailto in ("BOTH", "USER") : 1063 if mailto in ("BOTH", "USER") : 1064 1064 destination.append(usermail) 1065 1065 1066 1066 fullmessage = self.Reason + (_("\n\nYour system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail)) 1067 try : 1067 try : 1068 1068 server = smtplib.SMTP(self.smtpserver) 1069 except socket.error, msg : 1069 except socket.error, msg : 1070 1070 self.printInfo(_("Impossible to connect to SMTP server : %s") % msg, "error") 1071 1071 else : … … 1078 1078 if mailto == "BOTH" : 1079 1079 msg["Cc"] = adminmail 1080 else : 1080 else : 1081 1081 msg["To"] = adminmail 1082 1082 msg["Date"] = email.Utils.formatdate(localtime=True) 1083 1083 server.sendmail(adminmail, destination, msg.as_string()) 1084 except smtplib.SMTPException, answer : 1084 except smtplib.SMTPException, answer : 1085 1085 try : 1086 1086 for (k, v) in answer.recipients.items() : … … 1090 1090 server.quit() 1091 1091 self.logdebug("Feedback sent to user %s." % self.UserName) 1092 1093 def mainWork(self) : 1092 1093 def mainWork(self) : 1094 1094 """Main work is done here.""" 1095 1095 if not self.JobSizeBytes : … … 1101 1101 self.tellUser() 1102 1102 return self.removeJob() 1103 1103 1104 1104 self.getPrinterUserAndUserPQuota() 1105 1105 if self.Policy == "EXTERNALERROR" : … … 1116 1116 self.tellUser() 1117 1117 return self.removeJob() 1118 elif self.Policy == "DENY" : 1118 elif self.Policy == "DENY" : 1119 1119 # Either printer, user or user print quota doesn't exist, 1120 1120 # and the job should be rejected. … … 1134 1134 # be allowed if current user is allowed to print on this printer 1135 1135 return self.doWork() 1136 else : 1136 else : 1137 1137 self.Reason = _("Invalid policy %s for printer %s") % (self.Policy, self.PrinterName) 1138 1138 self.printInfo(self.Reason, "error") 1139 1139 self.tellUser() 1140 1140 return self.removeJob() 1141 1142 def doWork(self) : 1141 1142 def doWork(self) : 1143 1143 """The accounting work is done here.""" 1144 1144 self.precomputeJobPrice() … … 1146 1146 self.exportPrinterInfo() 1147 1147 self.exportPhaseInfo("BEFORE") 1148 1149 if self.Action not in ("DENY", "CANCEL") : 1148 1149 if self.Action not in ("DENY", "CANCEL") : 1150 1150 if self.Printer.MaxJobSize and (self.softwareJobSize > self.Printer.MaxJobSize) : 1151 1151 # This printer was set to refuse jobs this large. … … 1155 1155 # because in case of error the user could complain :-) 1156 1156 self.Reason = _("You are not allowed to print so many pages on printer %s at this time.") % self.PrinterName 1157 1157 1158 1158 if self.Action not in ("DENY", "CANCEL") : 1159 1159 if self.User.LimitBy == "noprint" : … … 1161 1161 self.Action = "DENY" 1162 1162 self.Reason = _("Your account settings forbid you to print at this time.") 1163 1163 1164 1164 if self.Action not in ("DENY", "CANCEL") : 1165 1165 # If printing is still allowed at this time, we … … 1168 1168 # save some database queries. 1169 1169 self.getBillingCode() 1170 1170 1171 1171 if self.Action not in ("DENY", "CANCEL") : 1172 1172 # If printing is still allowed at this time, we … … 1175 1175 # save some database queries. 1176 1176 self.checkIfDupe() 1177 1177 1178 1178 if self.Action not in ("DENY", "CANCEL") : 1179 1179 # If printing is still allowed at this time, we … … 1183 1183 if self.User.LimitBy in ('noquota', 'nochange') : 1184 1184 self.logdebug("User %s is allowed to print with no limit, no need to check quota." % self.UserName) 1185 elif self.Printer.PassThrough : 1185 elif self.Printer.PassThrough : 1186 1186 self.logdebug("Printer %s is in PassThrough mode, no need to check quota." % self.PrinterName) 1187 1187 else : … … 1194 1194 self.printInfo(_("Print Quota exceeded for user %s on printer %s") % (self.UserName, self.PrinterName)) 1195 1195 self.Reason = self.config.getHardWarn(self.PrinterName) 1196 elif self.Action == "WARN" : 1196 elif self.Action == "WARN" : 1197 1197 self.printInfo(_("Print Quota low for user %s on printer %s") % (self.UserName, self.PrinterName)) 1198 if self.User.LimitBy and (self.User.LimitBy.lower() == "balance") : 1198 if self.User.LimitBy and (self.User.LimitBy.lower() == "balance") : 1199 1199 self.Reason = self.config.getPoorWarn() 1200 else : 1200 else : 1201 1201 self.Reason = self.config.getSoftWarn(self.PrinterName) 1202 1203 # If job still allowed to print, should we ask for confirmation ? 1204 if self.Action not in ("DENY", "CANCEL") : 1202 1203 # If job still allowed to print, should we ask for confirmation ? 1204 if self.Action not in ("DENY", "CANCEL") : 1205 1205 if not self.didUserConfirm() : 1206 1206 self.Action = "CANCEL" 1207 1207 self.Reason = _("Print job cancelled.") 1208 1208 os.environ["PYKOTASTATUS"] = "CANCELLED" 1209 1209 1210 1210 # exports some new environment variables 1211 1211 self.exportReason() 1212 1212 1213 1213 # now tell the user if he needs to know something 1214 1214 self.tellUser() 1215 1215 1216 1216 # launches the pre hook 1217 1217 self.launchPreHook() 1218 1218 1219 1219 # handle starting banner pages without accounting 1220 1220 self.BannerSize = 0 … … 1222 1222 if (self.Action != "CANCEL") and accountbanner in ["ENDING", "NONE"] : 1223 1223 self.handleBanner("starting", 0) 1224 1224 1225 1225 if self.Action == "DENY" : 1226 1226 self.printInfo(_("Job denied, no accounting will be done.")) 1227 elif self.Action == "CANCEL" : 1227 elif self.Action == "CANCEL" : 1228 1228 self.printInfo(_("Job cancelled, no accounting will be done.")) 1229 1229 else : … … 1232 1232 self.accounter.beginJob(self.Printer) 1233 1233 self.installSigTermHandler() 1234 1234 1235 1235 # handle starting banner pages with accounting 1236 1236 if (self.Action != "CANCEL") and accountbanner in ["STARTING", "BOTH"] : 1237 1237 if not self.gotSigTerm : 1238 1238 self.handleBanner("starting", 1) 1239 1240 # pass the job's data to the real backend 1239 1240 # pass the job's data to the real backend 1241 1241 if (not self.gotSigTerm) and (self.Action in ["ALLOW", "WARN"]) : 1242 1242 retcode = self.printJobDatas() 1243 else : 1243 else : 1244 1244 retcode = self.removeJob() 1245 1245 1246 1246 # indicate phase change 1247 1247 self.exportPhaseInfo("AFTER") 1248 1248 1249 1249 # handle ending banner pages with accounting 1250 1250 if (self.Action != "CANCEL") and accountbanner in ["ENDING", "BOTH"] : 1251 1251 if not self.gotSigTerm : 1252 1252 self.handleBanner("ending", 1) 1253 1253 1254 1254 # stops accounting 1255 1255 if self.Action == "DENY" : 1256 1256 self.printInfo(_("Job denied, no accounting has been done.")) 1257 elif self.Action == "CANCEL" : 1257 elif self.Action == "CANCEL" : 1258 1258 self.printInfo(_("Job cancelled, no accounting has been done.")) 1259 1259 else : … … 1262 1262 self.installSigTermHandler() 1263 1263 self.printInfo(_("Job accounting ends.")) 1264 1265 # Do all these database changes within a single transaction 1264 1265 # Do all these database changes within a single transaction 1266 1266 # NB : we don't enclose ALL the changes within a single transaction 1267 1267 # because while waiting for the printer to answer its internal page … … 1280 1280 self.JobSize = 0 1281 1281 self.printInfo(_("Job size forced to 0 because the real CUPS backend failed. No accounting will be done."), "warn") 1282 else : 1282 else : 1283 1283 self.printInfo(_("The real CUPS backend failed, but the job will be accounted for anyway."), "warn") 1284 1285 # retrieve the job size 1284 1285 # retrieve the job size 1286 1286 if self.Action == "DENY" : 1287 1287 self.JobSize = 0 1288 1288 self.printInfo(_("Job size forced to 0 because printing is denied.")) 1289 elif self.Action == "CANCEL" : 1289 elif self.Action == "CANCEL" : 1290 1290 self.JobSize = 0 1291 1291 self.printInfo(_("Job size forced to 0 because printing was cancelled.")) 1292 else : 1292 else : 1293 1293 self.UserPQuota.resetDenyBannerCounter() 1294 if (self.Action != "PROBLEM") or ("CHARGE" in onbackenderror) : 1294 if (self.Action != "PROBLEM") or ("CHARGE" in onbackenderror) : 1295 1295 self.JobSize = self.accounter.getJobSize(self.Printer) 1296 1296 self.sanitizeJobSize() 1297 1297 self.JobSize += self.BannerSize 1298 1298 self.printInfo(_("Job size : %i") % self.JobSize) 1299 1299 1300 1300 if ((self.Action == "PROBLEM") and ("NOCHARGE" in onbackenderror)) or \ 1301 1301 (self.Action in ("DENY", "CANCEL")) : … … 1306 1306 self.JobPrice = 0.0 1307 1307 else : 1308 # update the quota for the current user on this printer 1308 # update the quota for the current user on this printer 1309 1309 self.printInfo(_("Updating user %s's quota on printer %s") % (self.UserName, self.PrinterName)) 1310 1310 self.JobPrice = self.UserPQuota.increasePagesUsage(self.JobSize, self.accounter.inkUsage) 1311 1312 # adds the current job to history 1311 1312 # adds the current job to history 1313 1313 self.Printer.addJobToHistory(self.JobId, self.User, self.accounter.getLastPageCounter(), \ 1314 1314 self.Action, self.JobSize, self.JobPrice, self.InputFile, \ … … 1317 1317 self.softwareJobSize, self.softwareJobPrice) 1318 1318 self.printInfo(_("Job added to history.")) 1319 1319 1320 1320 if hasattr(self, "BillingCode") and self.BillingCode and self.BillingCode.Exists : 1321 1321 if (self.Action in ("ALLOW", "WARN")) or \ … … 1323 1323 self.BillingCode.consume(self.JobSize, self.JobPrice) 1324 1324 self.printInfo(_("Billing code %s was updated.") % self.BillingCode.BillingCode) 1325 except : 1325 except : 1326 1326 self.storage.rollbackTransaction() 1327 1327 raise 1328 else : 1328 else : 1329 1329 self.storage.commitTransaction() 1330 1330 1331 1331 # exports some new environment variables 1332 1332 self.exportJobSizeAndPrice() 1333 1333 1334 1334 # then re-export user information with new values 1335 1335 self.exportUserInfo() 1336 1336 1337 1337 # handle ending banner pages without accounting 1338 1338 if (self.Action != "CANCEL") and accountbanner in ["STARTING", "NONE"] : 1339 1339 self.handleBanner("ending", 0) 1340 1340 1341 1341 self.launchPostHook() 1342 1343 return retcode 1344 1345 def printJobDatas(self) : 1342 1343 return retcode 1344 1345 def printJobDatas(self) : 1346 1346 """Sends the job's datas to the real backend.""" 1347 1347 self.logdebug("Sending job's datas to real backend...") 1348 1348 1349 1349 delay = 0 1350 1350 number = 1 … … 1355 1355 if (number < 0) or (delay < 0) : 1356 1356 raise ValueError 1357 except ValueError : 1357 except ValueError : 1358 1358 self.printInfo(_("Incorrect value for the 'onbackenderror' directive in section [%s]") % self.PrinterName, "error") 1359 1359 delay = 0 1360 1360 number = 1 1361 else : 1361 else : 1362 1362 break 1363 loopcnt = 1 1364 while True : 1363 loopcnt = 1 1364 while True : 1365 1365 if self.InputFile is None : 1366 1366 infile = open(self.DataFile, "rb") 1367 else : 1367 else : 1368 1368 infile = None 1369 1369 retcode = self.runOriginalBackend(infile) … … 1377 1377 time.sleep(delay) 1378 1378 loopcnt += 1 1379 else : 1379 else : 1380 1380 break 1381 1381 1382 1382 self.logdebug("Job's datas sent to real backend.") 1383 1383 return retcode 1384 1384 1385 1385 def runOriginalBackend(self, filehandle=None, isBanner=0) : 1386 1386 """Launches the original backend.""" … … 1388 1388 if not isBanner : 1389 1389 arguments = [os.environ["DEVICE_URI"]] + sys.argv[1:] 1390 else : 1390 else : 1391 1391 # For banners, we absolutely WANT 1392 1392 # to remove any filename from the command line ! … … 1397 1397 # TODO : do something about the job title : if we are printing a banner and the backend 1398 1398 # TODO : uses the job's title to name an output file (cups-pdf:// for example), we're stuck ! 1399 1399 1400 1400 self.logdebug("Starting original backend %s with args %s" % (originalbackend, " ".join(['"%s"' % a for a in arguments]))) 1401 self.regainPriv() 1401 self.regainPriv() 1402 1402 pid = os.fork() 1403 1403 self.logdebug("Forked !") … … 1411 1411 except OSError, msg : 1412 1412 self.logdebug("execve() failed: %s" % msg) 1413 self.logdebug("We shouldn't be there !!!") 1413 self.logdebug("We shouldn't be there !!!") 1414 1414 os._exit(-1) 1415 self.dropPriv() 1416 1417 self.logdebug("Waiting for original backend to exit...") 1415 self.dropPriv() 1416 1417 self.logdebug("Waiting for original backend to exit...") 1418 1418 killed = 0 1419 1419 status = -1 … … 1425 1425 os.kill(pid, signal.SIGTERM) 1426 1426 killed = 1 1427 1427 1428 1428 if os.WIFEXITED(status) : 1429 1429 status = os.WEXITSTATUS(status) … … 1433 1433 level = "error" 1434 1434 self.Reason = message 1435 else : 1435 else : 1436 1436 level = "info" 1437 1437 self.printInfo(message, level) … … 1445 1445 self.printInfo(self.Reason, "warn") 1446 1446 return 1 1447 1448 if __name__ == "__main__" : 1447 1448 if __name__ == "__main__" : 1449 1449 # This is a CUPS backend, we should act and die like a CUPS backend 1450 1450 wrapper = CUPSBackend() 1451 1451 if len(sys.argv) == 1 : 1452 1452 print "\n".join(wrapper.discoverOtherBackends()) 1453 sys.exit(0) 1454 elif len(sys.argv) not in (6, 7) : 1453 sys.exit(0) 1454 elif len(sys.argv) not in (6, 7) : 1455 1455 sys.stderr.write("ERROR: %s job-id user title copies options [file]\n"\ 1456 1456 % sys.argv[0]) 1457 1457 sys.exit(1) 1458 else : 1458 else : 1459 1459 os.environ["PATH"] = "%s:/bin:/usr/bin:/usr/local/bin:/opt/bin:/sbin:/usr/sbin" % os.environ.get("PATH", "") 1460 1460 try : … … 1466 1466 raise KeyboardInterrupt 1467 1467 wrapper.saveDatasAndCheckSum() 1468 wrapper.exportJobInfo() # exports a first time to give hints to external scripts 1468 1469 wrapper.preaccounter = openAccounter(wrapper, ispreaccounter=1) 1469 1470 wrapper.accounter = openAccounter(wrapper) 1470 1471 wrapper.precomputeJobSize() 1471 wrapper.exportJobInfo() # exports a first time to give hints to external scripts 1472 wrapper.exportJobInfo() # exports a first time to give hints to external scripts 1472 1473 wrapper.overwriteJobAttributes() 1473 1474 wrapper.exportJobInfo() # re-exports in case it was overwritten 1474 1475 retcode = wrapper.mainWork() 1475 except KeyboardInterrupt : 1476 except KeyboardInterrupt : 1476 1477 wrapper.printInfo(_("Job %s interrupted by the administrator !") % wrapper.JobId, "warn") 1477 1478 retcode = 0 1478 except SystemExit, err : 1479 except SystemExit, err : 1479 1480 retcode = err.code 1480 except : 1481 except : 1481 1482 try : 1482 1483 wrapper.crashed("cupspykota backend failed") 1483 except : 1484 except : 1484 1485 crashed("cupspykota backend failed") 1485 1486 retcode = 1 1486 finally : 1487 finally : 1487 1488 wrapper.clean() 1488 1489 sys.exit(retcode)