Changeset 3438
- Timestamp:
- 10/06/08 00:26:54 (3 months 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() <
