Changeset 3438
- Timestamp:
- 10/06/08 00:26:54 (16 years ago)
- Location:
- tea4cups/trunk
- Files:
-
- 6 modified
Legend:
- Unmodified
- Added
- Removed
-
tea4cups/trunk/CREDITS
r691 r3438 34 34 - Clive Bruton 35 35 - Mark Wolf - ESCO Corporation 36 36 37 37 38 38 Contributors : -
tea4cups/trunk/NEWS
r691 r3438 24 24 25 25 * 3.13alpha (2006-11-07) : 26 26 27 27 - Backported locking mechanism from PyKota. 28 28 29 29 * 3.12 (2006-08-11) : 30 31 - Serializes accesses to the same device from different queues 30 31 - Serializes accesses to the same device from different queues 32 32 or print servers through file locking facilities (works over 33 33 NFS). 34 34 35 35 - Improved support for CUPS 1.2.x and higher. 36 36 37 37 - Allows the administrator to cleanly stop tea4cups with SIGINT. 38 38 39 39 - Added a configurable retry directive to the configuration file. 40 40 41 41 - Fixed several minor bugs. 42 42 43 43 - Improved the installation instructions and the sample configuration 44 44 file. 45 46 * 3.11 : 47 45 46 * 3.11 : 47 48 48 - Fixed an incompatibility with Python 2.1, thanks to Frank 49 49 Koormann. 50 50 51 51 * 3.10 : 52 52 53 53 - Added filters. 54 54 55 55 - Added the "onfail" directive to tea4cups.conf 56 56 57 57 * 3.02 : 58 58 59 59 - Fixed some problems thanks to pychecker. 60 60 61 61 * 3.01 : 62 62 63 63 - Fixed an IPP parsing bug in some situations. 64 64 65 65 * 3.00 : 66 66 67 67 - Tees don't exist anymore. Only prehooks and posthooks remain. 68 68 69 69 - Prehooks can now send datas to Posthooks through pipes. 70 70 71 71 - Major rewrite of the subprocess management code, thanks to Peter Stuge. 72 72 73 73 * 2.12alpha : 74 74 -
tea4cups/trunk/README
r3382 r3438 24 24 25 25 Tea4CUPS behaves just like any other CUPS backend, but allows you to 26 modify print jobs' datas as they pass through it and to transparently 26 modify print jobs' datas as they pass through it and to transparently 27 27 send them to any number of outputs : other CUPS backends, files or pipes. 28 28 … … 54 54 print job has been sent to the real printer, 55 55 unless the job was previously cancelled by a 56 prehook. Any number of posthooks can be 56 prehook. Any number of posthooks can be 57 57 defined for a particular print queue. 58 58 … … 79 79 80 80 None of these environment variables is available to filters. 81 81 82 82 NB : Tea4CUPS requires a version of Python >= 2.3 83 83 … … 101 101 102 102 $ cp tea4cups /usr/lib/cups/backend 103 103 104 104 If you use CUPS v1.2 or higher you must do this as well : 105 105 106 106 $ chown root.root /usr/lib/cups/backend/tea4cups 107 107 $ chmod 700 /usr/lib/cups/backend/tea4cups … … 117 117 Or by directly modifying CUPS' printers.conf file, or with 118 118 the lpadmin command line tool, just prepend each DeviceURI 119 value with 'tea4cups://'. If you modified printers.conf 120 directly instead of using lpadmin then don't forget to 119 value with 'tea4cups://'. If you modified printers.conf 120 directly instead of using lpadmin then don't forget to 121 121 restart CUPS. 122 122 (Use this last method if autodetection doesn't work because -
tea4cups/trunk/tea4cups
r693 r3438 1 1 #! /usr/bin/env python 2 # -*- coding: ISO-8859-15-*-2 # -*- coding: utf-8 -*- 3 3 4 4 # Tea4CUPS : Tee for CUPS … … 35 35 the Free Software Foundation; either version 2 of the License, or 36 36 (at your option) any later version. 37 37 38 38 This program is distributed in the hope that it will be useful, 39 39 but WITHOUT ANY WARRANTY; without even the implied warranty of 40 40 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 41 41 GNU General Public License for more details. 42 42 43 43 You should have received a copy of the GNU General Public License 44 44 along with this program; if not, write to the Free Software … … 49 49 50 50 Copy the different files where they belong : 51 51 52 52 $ cp tea4cups /usr/lib/cups/backend/ 53 53 $ chown root.root /usr/lib/cups/backend/tea4cups 54 54 $ chmod 700 /usr/lib/cups/backend/tea4cups 55 55 $ cp tea4cups.conf /etc/cupsd/ 56 56 57 57 Now edit the configuration file to suit your needs : 58 58 59 59 $ vi /etc/cupsd/tea4cups.conf 60 60 61 61 NB : you can use emacs as well :-) 62 62 63 63 Finally restart CUPS : 64 64 65 65 $ /etc/init.d/cupsys restart 66 66 67 67 You can now create "Tea4CUPS Managed" print queues from 68 68 CUPS' web interface, or using lpadmin. 69 70 Send bug reports to : alet@librelogiciel.com 71 """ 69 70 Send bug reports to : alet@librelogiciel.com 71 """ 72 72 73 73 import sys … … 105 105 """IPP related exceptions.""" 106 106 pass 107 107 108 108 IPP_VERSION = "1.1" # default version number 109 109 … … 314 314 IPP_MULTIPLE_JOBS_NOT_SUPPORTED = 0x0509 315 315 IPP_PRINTER_IS_DEACTIVATED = 0x50a 316 316 317 317 CUPS_PRINTER_LOCAL = 0x0000 318 318 CUPS_PRINTER_CLASS = 0x0001 … … 341 341 CUPS_PRINTER_COMMANDS = 0x8000 342 342 CUPS_PRINTER_OPTIONS = 0xe6ff 343 343 344 344 class FakeAttribute : 345 345 """Fakes an IPPRequest attribute to simplify usage syntax.""" … … 348 348 self.request = request 349 349 self.name = name 350 350 351 351 def __setitem__(self, key, value) : 352 352 """Appends the value to the real attribute.""" … … 359 359 attribute[j][1].append(value) 360 360 return 361 attribute.append((key, [value])) 362 361 attribute.append((key, [value])) 362 363 363 def __getitem__(self, key) : 364 364 """Returns an attribute's value.""" … … 373 373 if answer : 374 374 return answer 375 raise KeyError, key 376 375 raise KeyError, key 376 377 377 class IPPRequest : 378 378 """A class for IPP requests.""" 379 379 attributes_types = ("operation", "job", "printer", "unsupported", \ 380 380 "subscription", "event_notification") 381 def __init__(self, data="", version=IPP_VERSION, 381 def __init__(self, data="", version=IPP_VERSION, 382 382 operation_id=None, \ 383 383 request_id=None, \ 384 384 debug=False) : 385 385 """Initializes an IPP Message object. 386 386 387 387 Parameters : 388 388 389 389 data : the complete IPP Message's content. 390 390 debug : a boolean value to output debug info on stderr. … … 393 393 self._data = data 394 394 self.parsed = False 395 395 396 396 # Initializes message 397 self.setVersion(version) 397 self.setVersion(version) 398 398 self.setOperationId(operation_id) 399 399 self.setRequestId(request_id) 400 400 self.data = "" 401 401 402 402 for attrtype in self.attributes_types : 403 403 setattr(self, "_%s_attributes" % attrtype, [[]]) 404 405 # Initialize tags 404 405 # Initialize tags 406 406 self.tags = [ None ] * 256 # by default all tags reserved 407 407 408 408 # Delimiter tags 409 409 self.tags[0x01] = "operation-attributes-tag" … … 414 414 self.tags[0x06] = "subscription-attributes-tag" 415 415 self.tags[0x07] = "event_notification-attributes-tag" 416 416 417 417 # out of band values 418 418 self.tags[0x10] = "unsupported" … … 423 423 self.tags[0x16] = "delete-attribute" 424 424 self.tags[0x17] = "admin-define" 425 425 426 426 # integer values 427 427 self.tags[0x20] = "generic-integer" … … 429 429 self.tags[0x22] = "boolean" 430 430 self.tags[0x23] = "enum" 431 431 432 432 # octetString 433 433 self.tags[0x30] = "octetString-with-an-unspecified-format" … … 439 439 self.tags[0x36] = "nameWithLanguage" 440 440 self.tags[0x37] = "endCollection" 441 441 442 442 # character strings 443 443 self.tags[0x40] = "generic-character-string" … … 451 451 self.tags[0x49] = "mimeMediaType" 452 452 self.tags[0x4a] = "memberAttrName" 453 453 454 454 # Reverse mapping to generate IPP messages 455 455 self.tagvalues = {} … … 458 458 if value is not None : 459 459 self.tagvalues[value] = i 460 461 def __getattr__(self, name) : 460 461 def __getattr__(self, name) : 462 462 """Fakes attribute access.""" 463 463 if name in self.attributes_types : … … 465 465 else : 466 466 raise AttributeError, name 467 468 def __str__(self) : 467 468 def __str__(self) : 469 469 """Returns the parsed IPP message in a readable form.""" 470 470 if not self.parsed : … … 480 480 for (name, value) in attribute : 481 481 mybuffer.append(" %s : %s" % (name, value)) 482 if self.data : 482 if self.data : 483 483 mybuffer.append("IPP datas : %s" % repr(self.data)) 484 484 return "\n".join(mybuffer) 485 486 def logDebug(self, msg) : 485 486 def logDebug(self, msg) : 487 487 """Prints a debug message.""" 488 488 if self.debug : 489 489 sys.stderr.write("%s\n" % msg) 490 490 sys.stderr.flush() 491 491 492 492 def setVersion(self, version) : 493 493 """Sets the request's operation id.""" … … 498 498 if len(version) == 2 : # 2-tuple 499 499 self.version = version 500 else : 500 else : 501 501 try : 502 502 self.version = [int(p) for p in str(float(version)).split(".")] 503 503 except : 504 504 self.version = [int(p) for p in IPP_VERSION.split(".")] 505 506 def setOperationId(self, opid) : 505 506 def setOperationId(self, opid) : 507 507 """Sets the request's operation id.""" 508 508 self.operation_id = opid 509 510 def setRequestId(self, reqid) : 509 510 def setRequestId(self, reqid) : 511 511 """Sets the request's request id.""" 512 512 self.request_id = reqid 513 514 def dump(self) : 513 514 def dump(self) : 515 515 """Generates an IPP Message. 516 516 517 517 Returns the message as a string of text. 518 """ 518 """ 519 519 mybuffer = [] 520 520 if None not in (self.version, self.operation_id) : … … 534 534 mybuffer.append(attrname) 535 535 nameprinted = 1 536 else : 536 else : 537 537 mybuffer.append(pack(">H", 0)) 538 538 if vtype in ("integer", "enum") : … … 542 542 mybuffer.append(pack(">H", 1)) 543 543 mybuffer.append(chr(val)) 544 else : 544 else : 545 545 mybuffer.append(pack(">H", len(val))) 546 546 mybuffer.append(val) 547 547 mybuffer.append(chr(self.tagvalues["end-of-attributes-tag"])) 548 mybuffer.append(self.data) 548 mybuffer.append(self.data) 549 549 return "".join(mybuffer) 550 550 551 551 def parse(self) : 552 552 """Parses an IPP Request. 553 553 554 554 NB : Only a subset of RFC2910 is implemented. 555 555 """ 556 556 self._curname = None 557 557 self._curattributes = None 558 558 559 559 self.setVersion((ord(self._data[0]), ord(self._data[1]))) 560 560 self.setOperationId(unpack(">H", self._data[2:4])[0]) … … 575 575 self.position -= 1 576 576 continue 577 oldtag = tag 577 oldtag = tag 578 578 tag = ord(self._data[self.position]) 579 579 if tag == oldtag : … … 581 581 except IndexError : 582 582 raise IPPError, "Unexpected end of IPP message." 583 584 self.data = self._data[self.position+1:] 583 584 self.data = self._data[self.position+1:] 585 585 self.parsed = True 586 587 def parseTag(self) : 586 587 def parseTag(self) : 588 588 """Extracts information from an IPP tag.""" 589 589 pos = self.position … … 594 594 if not namelength : 595 595 name = self._curname 596 else : 596 else : 597 597 posend += namelength 598 598 self._curname = name = self._data[pos2:posend] … … 603 603 if tagtype in ("integer", "enum") : 604 604 value = unpack(">I", value)[0] 605 elif tagtype == "boolean" : 605 elif tagtype == "boolean" : 606 606 value = ord(value) 607 try : 607 try : 608 608 (oldname, oldval) = self._curattributes[-1][-1] 609 609 if oldname == name : 610 610 oldval.append((tagtype, value)) 611 else : 611 else : 612 612 raise IndexError 613 except IndexError : 613 except IndexError : 614 614 self._curattributes[-1].append((name, [(tagtype, value)])) 615 615 self.logDebug("%s(%s) : %s" % (name, tagtype, value)) 616 616 return posend - self.position 617 618 def operation_attributes_tag(self) : 617 618 def operation_attributes_tag(self) : 619 619 """Indicates that the parser enters into an operation-attributes-tag group.""" 620 620 self._curattributes = self._operation_attributes 621 621 return self.parseTag() 622 623 def job_attributes_tag(self) : 622 623 def job_attributes_tag(self) : 624 624 """Indicates that the parser enters into a job-attributes-tag group.""" 625 625 self._curattributes = self._job_attributes 626 626 return self.parseTag() 627 628 def printer_attributes_tag(self) : 627 628 def printer_attributes_tag(self) : 629 629 """Indicates that the parser enters into a printer-attributes-tag group.""" 630 630 self._curattributes = self._printer_attributes 631 631 return self.parseTag() 632 633 def unsupported_attributes_tag(self) : 632 633 def unsupported_attributes_tag(self) : 634 634 """Indicates that the parser enters into an unsupported-attributes-tag group.""" 635 635 self._curattributes = self._unsupported_attributes 636 636 return self.parseTag() 637 638 def subscription_attributes_tag(self) : 637 638 def subscription_attributes_tag(self) : 639 639 """Indicates that the parser enters into a subscription-attributes-tag group.""" 640 640 self._curattributes = self._subscription_attributes 641 641 return self.parseTag() 642 643 def event_notification_attributes_tag(self) : 642 643 def event_notification_attributes_tag(self) : 644 644 """Indicates that the parser enters into an event-notification-attributes-tag group.""" 645 645 self._curattributes = self._event_notification_attributes 646 646 return self.parseTag() 647 648 647 648 649 649 class CUPS : 650 650 """A class for a CUPS instance.""" … … 655 655 if self.url.endswith("/") : 656 656 self.url = self.url[:-1] 657 else : 657 else : 658 658 self.url = self.getDefaultURL() 659 659 self.username = username … … 665 665 self.lastErrorMessage = None 666 666 self.requestId = None 667 668 def getDefaultURL(self) : 667 668 def getDefaultURL(self) : 669 669 """Builds a default URL.""" 670 670 # TODO : encryption methods. … … 675 675 # we can't handle this right now, so we use the default instead. 676 676 return "http://localhost:%s" % port 677 else : 677 else : 678 678 return "http://%s:%s" % (server, port) 679 679 680 680 def identifierToURI(self, service, ident) : 681 681 """Transforms an identifier into a particular URI depending on requested service.""" … … 683 683 service, 684 684 ident) 685 686 def nextRequestId(self) : 685 686 def nextRequestId(self) : 687 687 """Increments the current request id and returns the new value.""" 688 688 try : 689 689 self.requestId += 1 690 except TypeError : 690 except TypeError : 691 691 self.requestId = 1 692 692 return self.requestId 693 693 694 694 def newRequest(self, operationid=None) : 695 695 """Generates a new empty request.""" … … 701 701 req.operation["attributes-natural-language"] = ("naturalLanguage", self.language) 702 702 return req 703 703 704 704 def doRequest(self, req, url=None) : 705 705 """Sends a request to the CUPS server. 706 706 returns a new IPPRequest object, containing the parsed answer. 707 """ 707 """ 708 708 connexion = urllib2.Request(url=url or self.url, \ 709 709 data=req.dump()) … … 715 715 self.username, \ 716 716 self.password or "") 717 authhandler = urllib2.HTTPBasicAuthHandler(pwmanager) 717 authhandler = urllib2.HTTPBasicAuthHandler(pwmanager) 718 718 opener = urllib2.build_opener(authhandler) 719 719 urllib2.install_opener(opener) 720 self.lastError = None 720 self.lastError = None 721 721 self.lastErrorMessage = None 722 try : 722 try : 723 723 response = urllib2.urlopen(connexion) 724 except (urllib2.URLError, urllib2.HTTPError, socket.error), error : 724 except (urllib2.URLError, urllib2.HTTPError, socket.error), error : 725 725 self.lastError = error 726 726 self.lastErrorMessage = str(error) 727 727 return None 728 else : 728 else : 729 729 datas = response.read() 730 730 ippresponse = IPPRequest(datas) 731 731 ippresponse.parse() 732 732 return ippresponse 733 734 def getPPD(self, queuename) : 733 734 def getPPD(self, queuename) : 735 735 """Retrieves the PPD for a particular queuename.""" 736 736 req = self.newRequest(IPP_GET_PRINTER_ATTRIBUTES) … … 739 739 req.operation["requested-attributes"] = ("nameWithoutLanguage", attrib) 740 740 return self.doRequest(req) # TODO : get the PPD from the actual print server 741 741 742 742 def getDefault(self) : 743 743 """Retrieves CUPS' default printer.""" 744 744 return self.doRequest(self.newRequest(CUPS_GET_DEFAULT)) 745 746 def getJobAttributes(self, jobid) : 745 746 def getJobAttributes(self, jobid) : 747 747 """Retrieves a print job's attributes.""" 748 748 req = self.newRequest(IPP_GET_JOB_ATTRIBUTES) 749 749 req.operation["job-uri"] = ("uri", self.identifierToURI("jobs", jobid)) 750 750 return self.doRequest(req) 751 752 def getPrinters(self) : 751 752 def getPrinters(self) : 753 753 """Returns the list of print queues names.""" 754 754 req = self.newRequest(CUPS_GET_PRINTERS) … … 757 757 req.operation["printer-type-mask"] = ("enum", CUPS_PRINTER_CLASS) 758 758 return [printer[1] for printer in self.doRequest(req).printer["printer-name"]] 759 760 def getDevices(self) : 759 760 def getDevices(self) : 761 761 """Returns a list of devices as (deviceclass, deviceinfo, devicemakeandmodel, deviceuri) tuples.""" 762 762 answer = self.doRequest(self.newRequest(CUPS_GET_DEVICES)) … … 765 765 [d[1] for d in answer.printer["device-make-and-model"]], \ 766 766 [d[1] for d in answer.printer["device-uri"]]) 767 768 def getPPDs(self) : 767 768 def getPPDs(self) : 769 769 """Returns a list of PPDs as (ppdnaturallanguage, ppdmake, ppdmakeandmodel, ppdname) tuples.""" 770 770 answer = self.doRequest(self.newRequest(CUPS_GET_PPDS)) … … 773 773 [d[1] for d in answer.printer["ppd-make-and-model"]], \ 774 774 [d[1] for d in answer.printer["ppd-name"]]) 775 775 776 776 def createSubscription(self, uri, events=["all"], 777 777 userdata=None, … … 784 784 jobid=None) : 785 785 """Creates a job, printer or server subscription. 786 786 787 787 uri : the subscription's uri, e.g. ipp://server 788 788 events : a list of events to subscribe to, e.g. ["printer-added", "printer-deleted"] … … 794 794 timeinterval : the interval of time during notifications 795 795 jobid : the optional job id in case of a job subscription 796 """ 796 """ 797 797 if jobid is not None : 798 798 opid = IPP_CREATE_JOB_SUBSCRIPTION … … 805 805 for event in events : 806 806 req.subscription["notify-events"] = ("keyword", event) 807 if userdata is not None : 807 if userdata is not None : 808 808 req.subscription["notify-user-data"] = ("octetString-with-an-unspecified-format", userdata) 809 if recipient is not None : 809 if recipient is not None : 810 810 req.subscription["notify-recipient"] = ("uri", recipient) 811 811 if pullmethod is not None : … … 822 822 req.subscription["notify-job-id"] = ("integer", jobid) 823 823 return self.doRequest(req) 824 825 def cancelSubscription(self, uri, subscriptionid, jobid=None) : 824 825 def cancelSubscription(self, uri, subscriptionid, jobid=None) : 826 826 """Cancels a subscription. 827 827 828 828 uri : the subscription's uri. 829 829 subscriptionid : the subscription's id. … … 838 838 req.event_notification["notify-subscription-id"] = ("integer", subscriptionid) 839 839 return self.doRequest(req) 840 840 841 841 842 842 class FakeConfig : … … 879 879 conffile.close() 880 880 return dirvalues 881 881 882 882 class CupsBackend : 883 883 """Base class for tools with no database access.""" … … 912 912 self.LockFile = None 913 913 914 def waitForLock(self) : 914 def waitForLock(self) : 915 915 """Waits until we can acquire the lock file.""" 916 916 lockfilename = self.DeviceURI.replace("/", ".") … … 926 926 # open the lock file, optionally creating it if needed. 927 927 self.LockFile = open(lockfilename, "a+") 928 928 929 929 # we wait indefinitely for the lock to become available. 930 930 # works over NFS too. 931 931 fcntl.lockf(self.LockFile, fcntl.LOCK_EX) 932 932 haslock = True 933 933 934 934 self.logDebug("Lock %s acquired." % lockfilename) 935 935 936 936 # Here we save the PID in the lock file, but we don't use 937 937 # it, because the lock file may be in a directory shared … … 942 942 self.LockFile.write(str(self.pid)) 943 943 self.LockFile.flush() 944 except IOError : 944 except IOError : 945 945 self.logDebug("I/O Error while waiting for lock %s" % lockfilename) 946 946 time.sleep(0.25) 947 947 948 948 def readConfig(self) : 949 949 """Reads the configuration file.""" … … 1104 1104 self.Directory = self.getPrintQueueOption(self.PrinterName, "directory", ignore=1) or tempfile.gettempdir() 1105 1105 self.DataFile = os.path.join(self.Directory, "%s-%s-%s-%s" % (self.myname, self.PrinterName, self.UserName, self.JobId)) 1106 1106 1107 1107 # check that the DEVICE_URI environment variable's value is 1108 1108 # prefixed with self.myname otherwise don't touch it. … … 1128 1128 self.RealBackend = backend 1129 1129 self.DeviceURI = device_uri 1130 1130 1131 1131 try : 1132 1132 cupsserver = CUPS() # TODO : username and password and/or encryption … … 1135 1135 raise ValueError # don't hande unix domain sockets yet. 1136 1136 self.ControlFile = "NotUsedAnymore" 1137 except : 1137 except : 1138 1138 (ippfilename, answer) = self.parseIPPRequestFile() 1139 1139 self.ControlFile = ippfilename 1140 1140 1141 1141 try : 1142 1142 john = answer.job["job-originating-host-name"] 1143 except (KeyError, AttributeError) : 1143 except (KeyError, AttributeError) : 1144 1144 try : 1145 1145 john = answer.operation["job-originating-host-name"] 1146 except (KeyError, AttributeError) : 1146 except (KeyError, AttributeError) : 1147 1147 john = (None, None) 1148 if type(john) == type([]) : 1148 if type(john) == type([]) : 1149 1149 john = john[-1] 1150 (dummy, self.ClientHost) = john 1151 try : 1150 (dummy, self.ClientHost) = john 1151 try : 1152 1152 jbing = answer.job["job-billing"] 1153 except (KeyError, AttributeError) : 1153 except (KeyError, AttributeError) : 1154 1154 jbing = (None, None) 1155 if type(jbing) == type([]) : 1155 if type(jbing) == type([]) : 1156 1156 jbing = jbing[-1] 1157 1157 (dummy, self.JobBilling) = jbing 1158 1158 1159 1159 def parseIPPRequestFile(self) : 1160 1160 """Parses the IPP message file and returns a tuple (filename, parsedvalue).""" … … 1212 1212 else : 1213 1213 infile = sys.stdin 1214 1214 1215 1215 filtercommand = self.getPrintQueueOption(self.PrinterName, "filter", \ 1216 1216 ignore=1) 1217 if filtercommand : 1217 if filtercommand : 1218 1218 self.logDebug("Data stream will be filtered through [%s]" % filtercommand) 1219 1219 filteroutput = "%s.filteroutput" % self.DataFile … … 1226 1226 infile = open(filteroutput, "rb") 1227 1227 mustclose = 1 1228 else : 1228 else : 1229 1229 self.logDebug("Data stream will be used as-is (no filter defined)") 1230 1230 1231 1231 CHUNK = 64*1024 # read 64 Kb at a time 1232 1232 dummy = 0 … … 1245 1245 dummy += 1 1246 1246 outfile.close() 1247 1247 1248 1248 if filtercommand : 1249 1249 self.logDebug("Removing filter's output file %s" % filteroutput) 1250 1250 try : 1251 1251 os.remove(filteroutput) 1252 except : 1252 except : 1253 1253 pass 1254 1254 1255 1255 if mustclose : 1256 1256 infile.close() 1257 1257 1258 1258 self.logDebug("%s bytes saved..." % sizeread) 1259 1259 self.JobSize = sizeread … … 1269 1269 try : 1270 1270 os.remove(self.DataFile) 1271 except OSError, msg : 1271 except OSError, msg : 1272 1272 self.logInfo("Problem when removing %s : %s" % (self.DataFile, msg), "error") 1273 1273 1274 1274 if self.LockFile is not None : 1275 1275 self.logDebug("Removing lock...") … … 1277 1277 fcntl.lockf(self.LockFile, fcntl.LOCK_UN) 1278 1278 self.LockFile.close() 1279 except : 1279 except : 1280 1280 self.logInfo("Problem while unlocking.", "error") 1281 else : 1281 else : 1282 1282 self.logDebug("Lock removed.") 1283 1283 self.logDebug("Clean.") … … 1303 1303 self.logDebug("Launching onfail script %s" % onfail) 1304 1304 os.system(onfail) 1305 1305 1306 1306 os.environ["TEASTATUS"] = str(retcode) 1307 1307 branches = self.enumBranches(self.PrinterName, "posthook") 1308 1308 if self.runCommands("posthook", branches, serialize) : 1309 1309 self.logInfo("An error occured during the execution of posthooks.", "warn") 1310 1310 1311 1311 for p in [ (k, v) for (k, v) in self.pipes.items() if k != 0 ] : 1312 1312 os.close(p[1][0]) … … 1404 1404 try : 1405 1405 (number, delay) = [int(p) for p in retry.strip().split(",")] 1406 except (ValueError, AttributeError, TypeError) : 1406 except (ValueError, AttributeError, TypeError) : 1407 1407 self.logInfo("Invalid value '%s' for the 'retry' directive for printer %s in %s." % (retry, self.PrinterName, self.conffile), "error") 1408 1408 number = 1 1409 1409 delay = 0 1410 1411 loopcount = 1 1412 while 1 : 1410 1411 loopcount = 1 1412 while 1 : 1413 1413 retcode = self.runOriginalBackend() 1414 1414 if not retcode : … … 1419 1419 time.sleep(delay) 1420 1420 loopcount += 1 1421 else : 1421 else : 1422 1422 break 1423 return retcode 1424 1423 return retcode 1424 1425 1425 def runOriginalBackend(self) : 1426 1426 """Launches the original backend.""" … … 1435 1435 os.dup2(f.fileno(), 0) 1436 1436 f.close() 1437 else : 1437 else : 1438 1438 arguments[6] = self.DataFile # in case a tea4cups filter was applied 1439 1439 try : … … 1485 1485 except SystemExit, e : 1486 1486 returncode = e.code 1487 except KeyboardInterrupt : 1487 except KeyboardInterrupt : 1488 1488 wrapper.logInfo("Job %s interrupted by the administrator !" % wrapper.JobId, "warn") 1489 1489 except : … … 1498 1498 sys.stderr.flush() 1499 1499 returncode = 1 1500 finally : 1500 finally : 1501 1501 wrapper.cleanUp() 1502 1502 sys.exit(returncode) -
tea4cups/trunk/tea4cups.conf
r691 r3438 67 67 # 68 68 # N : number of times to try. If 0, will retry indefinitely, until 69 # the backend accepts all the datas without error. 69 # the backend accepts all the datas without error. 70 70 # 71 71 # S : delay in Seconds between two attempts. 72 72 # 73 73 # The example below would retry up to three times, at 60 seconds interval. 74 # 74 # 75 75 # retry : 3,60 76 76 … … 89 89 90 90 91 # Should we pass incoming datas through a filter command 91 # Should we pass incoming datas through a filter command 92 92 # BEFORE doing anything else ? 93 93 # NB : obvisouly the filter command doesn't have any access to … … 96 96 # The value defined in a print queue section takes precedence over the 97 97 # value defined in the [global] section. 98 # 98 # 99 99 # The sample filter below can remove the print job creation date 100 100 # from PostScript jobs, in order to more accurately detect duplicate … … 123 123 124 124 # When executing the contents of a prehook or posthook directive, as 125 # defined below, tea4cups makes the following environment variables 125 # defined below, tea4cups makes the following environment variables 126 126 # available to your own commands : 127 127 # … … 178 178 179 179 # Another example : a PDF generator which creates PDF documents 180 # in the user's home directory under the names JOB-iiii.pdf 180 # in the user's home directory under the names JOB-iiii.pdf 181 181 # where iiii is the job id : 182 182 # … … 231 231 # NB : Beware of some software which embed the job printing time into the 232 232 # PostScript job : two identical jobs may have different MD5 checksums 233 # if they differ only by the value of the '%%CreationDate:' PostScript 233 # if they differ only by the value of the '%%CreationDate:' PostScript 234 234 # comment ! 235 235 # -
tea4cups/trunk/TODO
r691 r3438 22 22 23 23 TODO, in no particular order : 24 24 25 25 - Give some complex examples. 26 26 27 27 ============================================================ 28 28