Changeset 3437 for pkipplib/trunk/pkipplib
- Timestamp:
- 10/06/08 00:24:42 (16 years ago)
- Location:
- pkipplib/trunk/pkipplib
- Files:
-
- 3 modified
Legend:
- Unmodified
- Added
- Removed
-
pkipplib/trunk/pkipplib/__init__.py
r45 r3437 1 # -*- coding: UTF-8 -*-1 # -*- coding: utf-8 -*- 2 2 # 3 3 # pkipplib : IPP and CUPS support for Python … … 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. -
pkipplib/trunk/pkipplib/pkipplib.py
r45 r3437 1 1 #! /usr/bin/env python 2 # -*- coding: UTF-8 -*-2 # -*- coding: utf-8 -*- 3 3 # 4 4 # pkipplib : IPP and CUPS support for Python … … 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 238 238 IPP_MULTIPLE_JOBS_NOT_SUPPORTED = 0x0509 239 239 IPP_PRINTER_IS_DEACTIVATED = 0x50a 240 240 241 241 CUPS_PRINTER_LOCAL = 0x0000 242 242 CUPS_PRINTER_CLASS = 0x0001 … … 265 265 CUPS_PRINTER_COMMANDS = 0x8000 266 266 CUPS_PRINTER_OPTIONS = 0xe6ff 267 268 267 268 269 269 class IPPError(Exception) : 270 270 """An exception for IPP related stuff.""" … … 282 282 self.request = request 283 283 self.name = name 284 284 285 285 def __setitem__(self, key, value) : 286 286 """Appends the value to the real attribute.""" … … 293 293 attribute[j][1].append(value) 294 294 return 295 attribute.append((key, [value])) 296 295 attribute.append((key, [value])) 296 297 297 def __getitem__(self, key) : 298 298 """Returns an attribute's value.""" … … 307 307 if answer : 308 308 return answer 309 raise KeyError, key 310 309 raise KeyError, key 310 311 311 class IPPRequest : 312 312 """A class for IPP requests.""" 313 313 attributes_types = ("operation", "job", "printer", "unsupported", \ 314 314 "subscription", "event_notification") 315 def __init__(self, data="", version=IPP_VERSION, 315 def __init__(self, data="", version=IPP_VERSION, 316 316 operation_id=None, \ 317 317 request_id=None, \ 318 318 debug=False) : 319 319 """Initializes an IPP Message object. 320 320 321 321 Parameters : 322 322 323 323 data : the complete IPP Message's content. 324 324 debug : a boolean value to output debug info on stderr. … … 327 327 self._data = data 328 328 self.parsed = False 329 329 330 330 # Initializes message 331 self.setVersion(version) 331 self.setVersion(version) 332 332 self.setOperationId(operation_id) 333 333 self.setRequestId(request_id) 334 334 self.data = "" 335 335 336 336 for attrtype in self.attributes_types : 337 337 setattr(self, "_%s_attributes" % attrtype, [[]]) 338 339 # Initialize tags 338 339 # Initialize tags 340 340 self.tags = [ None ] * 256 # by default all tags reserved 341 341 342 342 # Delimiter tags 343 343 self.tags[0x01] = "operation-attributes-tag" … … 348 348 self.tags[0x06] = "subscription-attributes-tag" 349 349 self.tags[0x07] = "event_notification-attributes-tag" 350 350 351 351 # out of band values 352 352 self.tags[0x10] = "unsupported" … … 357 357 self.tags[0x16] = "delete-attribute" 358 358 self.tags[0x17] = "admin-define" 359 359 360 360 # integer values 361 361 self.tags[0x20] = "generic-integer" … … 363 363 self.tags[0x22] = "boolean" 364 364 self.tags[0x23] = "enum" 365 365 366 366 # octetString 367 367 self.tags[0x30] = "octetString-with-an-unspecified-format" … … 373 373 self.tags[0x36] = "nameWithLanguage" 374 374 self.tags[0x37] = "endCollection" 375 375 376 376 # character strings 377 377 self.tags[0x40] = "generic-character-string" … … 385 385 self.tags[0x49] = "mimeMediaType" 386 386 self.tags[0x4a] = "memberAttrName" 387 387 388 388 # Reverse mapping to generate IPP messages 389 389 self.tagvalues = {} … … 392 392 if value is not None : 393 393 self.tagvalues[value] = i 394 395 def __getattr__(self, name) : 394 395 def __getattr__(self, name) : 396 396 """Fakes attribute access.""" 397 397 if name in self.attributes_types : … … 399 399 else : 400 400 raise AttributeError, name 401 402 def __str__(self) : 401 402 def __str__(self) : 403 403 """Returns the parsed IPP message in a readable form.""" 404 404 if not self.parsed : … … 414 414 for (name, value) in attribute : 415 415 mybuffer.append(" %s : %s" % (name, value)) 416 if self.data : 416 if self.data : 417 417 mybuffer.append("IPP datas : %s" % repr(self.data)) 418 418 return "\n".join(mybuffer) 419 420 def logDebug(self, msg) : 419 420 def logDebug(self, msg) : 421 421 """Prints a debug message.""" 422 422 if self.debug : 423 423 sys.stderr.write("%s\n" % msg) 424 424 sys.stderr.flush() 425 425 426 426 def setVersion(self, version) : 427 427 """Sets the request's operation id.""" … … 432 432 if len(version) == 2 : # 2-tuple 433 433 self.version = version 434 else : 434 else : 435 435 try : 436 436 self.version = [int(p) for p in str(float(version)).split(".")] 437 437 except : 438 438 self.version = [int(p) for p in IPP_VERSION.split(".")] 439 440 def setOperationId(self, opid) : 439 440 def setOperationId(self, opid) : 441 441 """Sets the request's operation id.""" 442 442 self.operation_id = opid 443 444 def setRequestId(self, reqid) : 443 444 def setRequestId(self, reqid) : 445 445 """Sets the request's request id.""" 446 446 self.request_id = reqid 447 448 def dump(self) : 447 448 def dump(self) : 449 449 """Generates an IPP Message. 450 450 451 451 Returns the message as a string of text. 452 """ 452 """ 453 453 mybuffer = [] 454 454 if None not in (self.version, self.operation_id) : … … 468 468 mybuffer.append(attrname) 469 469 nameprinted = 1 470 else : 470 else : 471 471 mybuffer.append(pack(">H", 0)) 472 472 if vtype in ("integer", "enum") : … … 476 476 mybuffer.append(pack(">H", 1)) 477 477 mybuffer.append(chr(val)) 478 else : 478 else : 479 479 mybuffer.append(pack(">H", len(val))) 480 480 mybuffer.append(val) 481 481 mybuffer.append(chr(self.tagvalues["end-of-attributes-tag"])) 482 mybuffer.append(self.data) 482 mybuffer.append(self.data) 483 483 return "".join(mybuffer) 484 484 485 485 def parse(self) : 486 486 """Parses an IPP Request. 487 487 488 488 NB : Only a subset of RFC2910 is implemented. 489 489 """ 490 490 self._curname = None 491 491 self._curattributes = None 492 492 493 493 try : 494 494 self.setVersion((ord(self._data[0]), ord(self._data[1]))) … … 509 509 self.position -= 1 510 510 continue 511 oldtag = tag 511 oldtag = tag 512 512 tag = ord(self._data[self.position]) 513 513 if tag == oldtag : … … 515 515 except IndexError : 516 516 raise IPPError, "Unexpected end of IPP message." 517 518 self.data = self._data[self.position+1:] 517 518 self.data = self._data[self.position+1:] 519 519 self.parsed = True 520 521 def parseTag(self) : 520 521 def parseTag(self) : 522 522 """Extracts information from an IPP tag.""" 523 523 pos = self.position … … 528 528 if not namelength : 529 529 name = self._curname 530 else : 530 else : 531 531 posend += namelength 532 532 self._curname = name = self._data[pos2:posend] … … 537 537 if tagtype in ("integer", "enum") : 538 538 value = unpack(">I", value)[0] 539 elif tagtype == "boolean" : 539 elif tagtype == "boolean" : 540 540 value = ord(value) 541 try : 541 try : 542 542 (oldname, oldval) = self._curattributes[-1][-1] 543 543 if oldname == name : 544 544 oldval.append((tagtype, value)) 545 else : 545 else : 546 546 raise IndexError 547 except IndexError : 547 except IndexError : 548 548 self._curattributes[-1].append((name, [(tagtype, value)])) 549 549 self.logDebug("%s(%s) : %s" % (name, tagtype, value)) 550 550 return posend - self.position 551 552 def operation_attributes_tag(self) : 551 552 def operation_attributes_tag(self) : 553 553 """Indicates that the parser enters into an operation-attributes-tag group.""" 554 554 self._curattributes = self._operation_attributes 555 555 return self.parseTag() 556 557 def job_attributes_tag(self) : 556 557 def job_attributes_tag(self) : 558 558 """Indicates that the parser enters into a job-attributes-tag group.""" 559 559 self._curattributes = self._job_attributes 560 560 return self.parseTag() 561 562 def printer_attributes_tag(self) : 561 562 def printer_attributes_tag(self) : 563 563 """Indicates that the parser enters into a printer-attributes-tag group.""" 564 564 self._curattributes = self._printer_attributes 565 565 return self.parseTag() 566 567 def unsupported_attributes_tag(self) : 566 567 def unsupported_attributes_tag(self) : 568 568 """Indicates that the parser enters into an unsupported-attributes-tag group.""" 569 569 self._curattributes = self._unsupported_attributes 570 570 return self.parseTag() 571 572 def subscription_attributes_tag(self) : 571 572 def subscription_attributes_tag(self) : 573 573 """Indicates that the parser enters into a subscription-attributes-tag group.""" 574 574 self._curattributes = self._subscription_attributes 575 575 return self.parseTag() 576 577 def event_notification_attributes_tag(self) : 576 577 def event_notification_attributes_tag(self) : 578 578 """Indicates that the parser enters into an event-notification-attributes-tag group.""" 579 579 self._curattributes = self._event_notification_attributes 580 580 return self.parseTag() 581 582 581 582 583 583 class CUPS : 584 584 """A class for a CUPS instance.""" … … 589 589 if self.url.endswith("/") : 590 590 self.url = self.url[:-1] 591 else : 591 else : 592 592 self.url = self.getDefaultURL() 593 593 594 594 # If no username or password, use the ones set by CUPS, if any 595 595 self.username = username or os.environ.get("AUTH_USERNAME") 596 596 self.password = password or os.environ.get("AUTH_PASSWORD") 597 597 598 598 self.charset = charset 599 599 self.language = language … … 602 602 self.lastErrorMessage = None 603 603 self.requestId = None 604 605 def getDefaultURL(self) : 604 605 def getDefaultURL(self) : 606 606 """Builds a default URL.""" 607 607 # TODO : encryption methods. … … 613 613 return "http://localhost:%s" % port 614 614 #return "socket:%s" % server # TODO : make this work ! 615 else : 615 else : 616 616 return "http://%s:%s" % (server, port) 617 617 618 618 def identifierToURI(self, service, ident) : 619 619 """Transforms an identifier into a particular URI depending on requested service.""" … … 621 621 service, 622 622 ident) 623 624 def nextRequestId(self) : 623 624 def nextRequestId(self) : 625 625 """Increments the current request id and returns the new value.""" 626 626 try : 627 627 self.requestId += 1 628 except TypeError : 628 except TypeError : 629 629 self.requestId = 1 630 630 return self.requestId 631 631 632 632 def newRequest(self, operationid=None) : 633 633 """Generates a new empty request.""" … … 641 641 req.operation["requesting-user-name"] = ("nameWithoutLanguage", self.username) 642 642 return req 643 643 644 644 def doRequest(self, req, url=None) : 645 645 """Sends a request to the CUPS server. 646 646 returns a new IPPRequest object, containing the parsed answer. 647 """ 647 """ 648 648 url = url or self.url 649 649 connection = urllib2.Request(url=url, \ … … 657 657 self.username, \ 658 658 self.password or "") 659 authhandler = urllib2.HTTPBasicAuthHandler(pwmanager) 659 authhandler = urllib2.HTTPBasicAuthHandler(pwmanager) 660 660 opener = urllib2.build_opener(authhandler) 661 661 urllib2.install_opener(opener) 662 else : # TODO : also do this in the 'if' part 663 if url.startswith("socket:") : 662 else : # TODO : also do this in the 'if' part 663 if url.startswith("socket:") : 664 664 class SocketHandler(urllib2.HTTPHandler) : 665 665 """A class to handle IPP connections over an Unix domain socket.""" … … 675 675 sys.stderr.write("Opened [%s]\n" % req.get_selector()) 676 676 return s.makefile(mode="r+b") 677 678 opener = urllib2.build_opener(SocketHandler()) 677 678 opener = urllib2.build_opener(SocketHandler()) 679 679 urllib2.install_opener(opener) 680 680 sys.stderr.write("Opener installed\n") 681 self.lastError = None 681 self.lastError = None 682 682 self.lastErrorMessage = None 683 try : 683 try : 684 684 response = urllib2.urlopen(connection) 685 except (urllib2.URLError, urllib2.HTTPError, socket.error), error : 685 except (urllib2.URLError, urllib2.HTTPError, socket.error), error : 686 686 self.lastError = error 687 687 self.lastErrorMessage = str(error) 688 688 return None 689 else : 689 else : 690 690 bytes = [] 691 691 try : … … 694 694 try : 695 695 byte = response.read(1) 696 except AttributeError : 696 except AttributeError : 697 697 byte = response.recv(1) 698 698 if not byte : 699 699 break 700 else : 700 else : 701 701 bytes.append(byte) 702 702 except socket.error : … … 711 711 else : 712 712 return None 713 714 def getPPD(self, queuename) : 713 714 def getPPD(self, queuename) : 715 715 """Retrieves the PPD for a particular queuename.""" 716 716 req = self.newRequest(IPP_GET_PRINTER_ATTRIBUTES) … … 719 719 req.operation["requested-attributes"] = ("nameWithoutLanguage", attrib) 720 720 return self.doRequest(req) # TODO : get the PPD from the actual print server 721 721 722 722 def getDefault(self) : 723 723 """Retrieves CUPS' default printer.""" 724 724 return self.doRequest(self.newRequest(CUPS_GET_DEFAULT)) 725 726 def getJobAttributes(self, jobid) : 725 726 def getJobAttributes(self, jobid) : 727 727 """Retrieves a print job's attributes.""" 728 728 req = self.newRequest(IPP_GET_JOB_ATTRIBUTES) 729 729 req.operation["job-uri"] = ("uri", self.identifierToURI("jobs", jobid)) 730 730 return self.doRequest(req) 731 732 def getPrinters(self) : 731 732 def getPrinters(self) : 733 733 """Returns the list of print queues names.""" 734 734 req = self.newRequest(CUPS_GET_PRINTERS) … … 737 737 req.operation["printer-type-mask"] = ("enum", CUPS_PRINTER_CLASS) 738 738 return [printer[1] for printer in self.doRequest(req).printer["printer-name"]] 739 740 def getDevices(self) : 739 740 def getDevices(self) : 741 741 """Returns a list of devices as (deviceclass, deviceinfo, devicemakeandmodel, deviceuri) tuples.""" 742 742 answer = self.doRequest(self.newRequest(CUPS_GET_DEVICES)) … … 745 745 [d[1] for d in answer.printer["device-make-and-model"]], \ 746 746 [d[1] for d in answer.printer["device-uri"]]) 747 748 def getPPDs(self) : 747 748 def getPPDs(self) : 749 749 """Returns a list of PPDs as (ppdnaturallanguage, ppdmake, ppdmakeandmodel, ppdname) tuples.""" 750 750 answer = self.doRequest(self.newRequest(CUPS_GET_PPDS)) … … 753 753 [d[1] for d in answer.printer["ppd-make-and-model"]], \ 754 754 [d[1] for d in answer.printer["ppd-name"]]) 755 755 756 756 def createSubscription(self, uri, events=["all"], 757 757 userdata=None, … … 764 764 jobid=None) : 765 765 """Creates a job, printer or server subscription. 766 766 767 767 uri : the subscription's uri, e.g. ipp://server 768 768 events : a list of events to subscribe to, e.g. ["printer-added", "printer-deleted"] … … 774 774 timeinterval : the interval of time during notifications 775 775 jobid : the optional job id in case of a job subscription 776 """ 776 """ 777 777 if jobid is not None : 778 778 opid = IPP_CREATE_JOB_SUBSCRIPTION … … 785 785 for event in events : 786 786 req.subscription["notify-events"] = ("keyword", event) 787 if userdata is not None : 787 if userdata is not None : 788 788 req.subscription["notify-user-data"] = ("octetString-with-an-unspecified-format", userdata) 789 if recipient is not None : 789 if recipient is not None : 790 790 req.subscription["notify-recipient"] = ("uri", recipient) 791 791 if pullmethod is not None : … … 802 802 req.subscription["notify-job-id"] = ("integer", jobid) 803 803 return self.doRequest(req) 804 805 def cancelSubscription(self, uri, subscriptionid, jobid=None) : 804 805 def cancelSubscription(self, uri, subscriptionid, jobid=None) : 806 806 """Cancels a subscription. 807 807 808 808 uri : the subscription's uri. 809 809 subscriptionid : the subscription's id. … … 818 818 req.event_notification["notify-subscription-id"] = ("integer", subscriptionid) 819 819 return self.doRequest(req) 820 821 if __name__ == "__main__" : 820 821 if __name__ == "__main__" : 822 822 if (len(sys.argv) < 2) or (sys.argv[1] == "--debug") : 823 823 print "usage : python pkipplib.py /var/spool/cups/c00005 [--debug] (for example)\n" 824 else : 824 else : 825 825 infile = open(sys.argv[1], "rb") 826 826 filedata = infile.read() 827 827 infile.close() 828 828 829 829 msg = IPPRequest(filedata, debug=(sys.argv[-1]=="--debug")) 830 830 msg.parse() … … 832 832 msg2.parse() 833 833 filedata2 = msg2.dump() 834 834 835 835 if filedata == filedata2 : 836 836 print "Test OK : parsing original and parsing the output of the dump produce the same dump !" 837 837 print str(msg) 838 else : 838 else : 839 839 print "Test Failed !" 840 840 print str(msg) 841 841 print 842 842 print str(msg2) 843 843 -
pkipplib/trunk/pkipplib/version.py
r45 r3437 1 # -*- coding: UTF-8 -*-1 # -*- coding: utf-8 -*- 2 2 # 3 3 # pkipplib : IPP and CUPS support for Python … … 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>.