Changeset 677

Show
Ignore:
Timestamp:
06/28/06 00:18:34 (17 years ago)
Author:
jerome
Message:

Better support for CUPS v1.2.x and higher.
Fixed a Copy&Paste artifact of code coming from PyKota.
Now includes a copy of pkipplib v0.07 internally, and uses it by default,
only falling back to the old method if an error occurs.

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • tea4cups/trunk/tea4cups

    r676 r677  
    3535import ConfigParser 
    3636import signal 
     37import socket 
     38import urllib2 
    3739from struct import pack, unpack 
    3840 
    39 __version__ = "3.11_unofficial" 
     41__version__ = "3.12alpha_unofficial" 
    4042 
    4143class TeeError(Exception): 
     
    5557    """IPP related exceptions.""" 
    5658    pass 
    57  
     59     
     60IPP_VERSION = "1.1"     # default version number 
     61 
     62IPP_PORT = 631 
     63 
     64IPP_MAX_NAME = 256 
     65IPP_MAX_VALUES = 8 
     66 
     67IPP_TAG_ZERO = 0x00 
     68IPP_TAG_OPERATION = 0x01 
     69IPP_TAG_JOB = 0x02 
     70IPP_TAG_END = 0x03 
     71IPP_TAG_PRINTER = 0x04 
     72IPP_TAG_UNSUPPORTED_GROUP = 0x05 
     73IPP_TAG_SUBSCRIPTION = 0x06 
     74IPP_TAG_EVENT_NOTIFICATION = 0x07 
     75IPP_TAG_UNSUPPORTED_VALUE = 0x10 
     76IPP_TAG_DEFAULT = 0x11 
     77IPP_TAG_UNKNOWN = 0x12 
     78IPP_TAG_NOVALUE = 0x13 
     79IPP_TAG_NOTSETTABLE = 0x15 
     80IPP_TAG_DELETEATTR = 0x16 
     81IPP_TAG_ADMINDEFINE = 0x17 
     82IPP_TAG_INTEGER = 0x21 
     83IPP_TAG_BOOLEAN = 0x22 
     84IPP_TAG_ENUM = 0x23 
     85IPP_TAG_STRING = 0x30 
     86IPP_TAG_DATE = 0x31 
     87IPP_TAG_RESOLUTION = 0x32 
     88IPP_TAG_RANGE = 0x33 
     89IPP_TAG_BEGIN_COLLECTION = 0x34 
     90IPP_TAG_TEXTLANG = 0x35 
     91IPP_TAG_NAMELANG = 0x36 
     92IPP_TAG_END_COLLECTION = 0x37 
     93IPP_TAG_TEXT = 0x41 
     94IPP_TAG_NAME = 0x42 
     95IPP_TAG_KEYWORD = 0x44 
     96IPP_TAG_URI = 0x45 
     97IPP_TAG_URISCHEME = 0x46 
     98IPP_TAG_CHARSET = 0x47 
     99IPP_TAG_LANGUAGE = 0x48 
     100IPP_TAG_MIMETYPE = 0x49 
     101IPP_TAG_MEMBERNAME = 0x4a 
     102IPP_TAG_MASK = 0x7fffffff 
     103IPP_TAG_COPY = -0x7fffffff-1 
     104 
     105IPP_RES_PER_INCH = 3 
     106IPP_RES_PER_CM = 4 
     107 
     108IPP_FINISHINGS_NONE = 3 
     109IPP_FINISHINGS_STAPLE = 4 
     110IPP_FINISHINGS_PUNCH = 5 
     111IPP_FINISHINGS_COVER = 6 
     112IPP_FINISHINGS_BIND = 7 
     113IPP_FINISHINGS_SADDLE_STITCH = 8 
     114IPP_FINISHINGS_EDGE_STITCH = 9 
     115IPP_FINISHINGS_FOLD = 10 
     116IPP_FINISHINGS_TRIM = 11 
     117IPP_FINISHINGS_BALE = 12 
     118IPP_FINISHINGS_BOOKLET_MAKER = 13 
     119IPP_FINISHINGS_JOB_OFFSET = 14 
     120IPP_FINISHINGS_STAPLE_TOP_LEFT = 20 
     121IPP_FINISHINGS_STAPLE_BOTTOM_LEFT = 21 
     122IPP_FINISHINGS_STAPLE_TOP_RIGHT = 22 
     123IPP_FINISHINGS_STAPLE_BOTTOM_RIGHT = 23 
     124IPP_FINISHINGS_EDGE_STITCH_LEFT = 24 
     125IPP_FINISHINGS_EDGE_STITCH_TOP = 25 
     126IPP_FINISHINGS_EDGE_STITCH_RIGHT = 26 
     127IPP_FINISHINGS_EDGE_STITCH_BOTTOM = 27 
     128IPP_FINISHINGS_STAPLE_DUAL_LEFT = 28 
     129IPP_FINISHINGS_STAPLE_DUAL_TOP = 29 
     130IPP_FINISHINGS_STAPLE_DUAL_RIGHT = 30 
     131IPP_FINISHINGS_STAPLE_DUAL_BOTTOM = 31 
     132IPP_FINISHINGS_BIND_LEFT = 50 
     133IPP_FINISHINGS_BIND_TOP = 51 
     134IPP_FINISHINGS_BIND_RIGHT = 52 
     135IPP_FINISHINGS_BIND_BOTTO = 53 
     136 
     137IPP_PORTRAIT = 3 
     138IPP_LANDSCAPE = 4 
     139IPP_REVERSE_LANDSCAPE = 5 
     140IPP_REVERSE_PORTRAIT = 6 
     141 
     142IPP_QUALITY_DRAFT = 3 
     143IPP_QUALITY_NORMAL = 4 
     144IPP_QUALITY_HIGH = 5 
     145 
     146IPP_JOB_PENDING = 3 
     147IPP_JOB_HELD = 4 
     148IPP_JOB_PROCESSING = 5 
     149IPP_JOB_STOPPED = 6 
     150IPP_JOB_CANCELLED = 7 
     151IPP_JOB_ABORTED = 8 
     152IPP_JOB_COMPLETE = 9 
     153 
     154IPP_PRINTER_IDLE = 3 
     155IPP_PRINTER_PROCESSING = 4 
     156IPP_PRINTER_STOPPED = 5 
     157 
     158IPP_ERROR = -1 
     159IPP_IDLE = 0 
     160IPP_HEADER = 1 
     161IPP_ATTRIBUTE = 2 
     162IPP_DATA = 3 
     163 
     164IPP_PRINT_JOB = 0x0002 
     165IPP_PRINT_URI = 0x0003 
     166IPP_VALIDATE_JOB = 0x0004 
     167IPP_CREATE_JOB = 0x0005 
     168IPP_SEND_DOCUMENT = 0x0006 
     169IPP_SEND_URI = 0x0007 
     170IPP_CANCEL_JOB = 0x0008 
     171IPP_GET_JOB_ATTRIBUTES = 0x0009 
     172IPP_GET_JOBS = 0x000a 
     173IPP_GET_PRINTER_ATTRIBUTES = 0x000b 
     174IPP_HOLD_JOB = 0x000c 
     175IPP_RELEASE_JOB = 0x000d 
     176IPP_RESTART_JOB = 0x000e 
     177IPP_PAUSE_PRINTER = 0x0010 
     178IPP_RESUME_PRINTER = 0x0011 
     179IPP_PURGE_JOBS = 0x0012 
     180IPP_SET_PRINTER_ATTRIBUTES = 0x0013 
     181IPP_SET_JOB_ATTRIBUTES = 0x0014 
     182IPP_GET_PRINTER_SUPPORTED_VALUES = 0x0015 
     183IPP_CREATE_PRINTER_SUBSCRIPTION = 0x0016 
     184IPP_CREATE_JOB_SUBSCRIPTION = 0x0017 
     185IPP_GET_SUBSCRIPTION_ATTRIBUTES = 0x0018 
     186IPP_GET_SUBSCRIPTIONS = 0x0019 
     187IPP_RENEW_SUBSCRIPTION = 0x001a 
     188IPP_CANCEL_SUBSCRIPTION = 0x001b 
     189IPP_GET_NOTIFICATIONS = 0x001c 
     190IPP_SEND_NOTIFICATIONS = 0x001d 
     191IPP_GET_PRINT_SUPPORT_FILES = 0x0021 
     192IPP_ENABLE_PRINTER = 0x0022 
     193IPP_DISABLE_PRINTER = 0x0023 
     194IPP_PAUSE_PRINTER_AFTER_CURRENT_JOB = 0x0024 
     195IPP_HOLD_NEW_JOBS = 0x0025 
     196IPP_RELEASE_HELD_NEW_JOBS = 0x0026 
     197IPP_DEACTIVATE_PRINTER = 0x0027 
     198IPP_ACTIVATE_PRINTER = 0x0028 
     199IPP_RESTART_PRINTER = 0x0029 
     200IPP_SHUTDOWN_PRINTER = 0x002a 
     201IPP_STARTUP_PRINTER = 0x002b 
     202IPP_REPROCESS_JOB = 0x002c 
     203IPP_CANCEL_CURRENT_JOB = 0x002d 
     204IPP_SUSPEND_CURRENT_JOB = 0x002e 
     205IPP_RESUME_JOB = 0x002f 
     206IPP_PROMOTE_JOB = 0x0030 
     207IPP_SCHEDULE_JOB_AFTER = 0x0031 
     208IPP_PRIVATE = 0x4000 
     209CUPS_GET_DEFAULT = 0x4001 
     210CUPS_GET_PRINTERS = 0x4002 
     211CUPS_ADD_PRINTER = 0x4003 
     212CUPS_DELETE_PRINTER = 0x4004 
     213CUPS_GET_CLASSES = 0x4005 
     214CUPS_ADD_CLASS = 0x4006 
     215CUPS_DELETE_CLASS = 0x4007 
     216CUPS_ACCEPT_JOBS = 0x4008 
     217CUPS_REJECT_JOBS = 0x4009 
     218CUPS_SET_DEFAULT = 0x400a 
     219CUPS_GET_DEVICES = 0x400b 
     220CUPS_GET_PPDS = 0x400c 
     221CUPS_MOVE_JOB = 0x400d 
     222CUPS_AUTHENTICATE_JOB = 0x400e 
     223 
     224IPP_OK = 0x0000 
     225IPP_OK_SUBST = 0x0001 
     226IPP_OK_CONFLICT = 0x0002 
     227IPP_OK_IGNORED_SUBSCRIPTIONS = 0x0003 
     228IPP_OK_IGNORED_NOTIFICATIONS = 0x0004 
     229IPP_OK_TOO_MANY_EVENTS = 0x0005 
     230IPP_OK_BUT_CANCEL_SUBSCRIPTION = 0x0006 
     231IPP_REDIRECTION_OTHER_SITE = 0x0300 
     232IPP_BAD_REQUEST = 0x0400 
     233IPP_FORBIDDEN = 0x0401 
     234IPP_NOT_AUTHENTICATED = 0x0402 
     235IPP_NOT_AUTHORIZED = 0x0403 
     236IPP_NOT_POSSIBLE = 0x0404 
     237IPP_TIMEOUT = 0x0405 
     238IPP_NOT_FOUND = 0x0406 
     239IPP_GONE = 0x0407 
     240IPP_REQUEST_ENTITY = 0x0408 
     241IPP_REQUEST_VALUE = 0x0409 
     242IPP_DOCUMENT_FORMAT = 0x040a 
     243IPP_ATTRIBUTES = 0x040b 
     244IPP_URI_SCHEME = 0x040c 
     245IPP_CHARSET = 0x040d 
     246IPP_CONFLICT = 0x040e 
     247IPP_COMPRESSION_NOT_SUPPORTED = 0x040f 
     248IPP_COMPRESSION_ERROR = 0x0410 
     249IPP_DOCUMENT_FORMAT_ERROR = 0x0411 
     250IPP_DOCUMENT_ACCESS_ERROR = 0x0412 
     251IPP_ATTRIBUTES_NOT_SETTABLE = 0x0413 
     252IPP_IGNORED_ALL_SUBSCRIPTIONS = 0x0414 
     253IPP_TOO_MANY_SUBSCRIPTIONS = 0x0415 
     254IPP_IGNORED_ALL_NOTIFICATIONS = 0x0416 
     255IPP_PRINT_SUPPORT_FILE_NOT_FOUND = 0x0417 
     256 
     257IPP_INTERNAL_ERROR = 0x0500 
     258IPP_OPERATION_NOT_SUPPORTED = 0x0501 
     259IPP_SERVICE_UNAVAILABLE = 0x0502 
     260IPP_VERSION_NOT_SUPPORTED = 0x0503 
     261IPP_DEVICE_ERROR = 0x0504 
     262IPP_TEMPORARY_ERROR = 0x0505 
     263IPP_NOT_ACCEPTING = 0x0506 
     264IPP_PRINTER_BUSY = 0x0507 
     265IPP_ERROR_JOB_CANCELLED = 0x0508 
     266IPP_MULTIPLE_JOBS_NOT_SUPPORTED = 0x0509 
     267IPP_PRINTER_IS_DEACTIVATED = 0x50a 
     268   
     269CUPS_PRINTER_LOCAL = 0x0000 
     270CUPS_PRINTER_CLASS = 0x0001 
     271CUPS_PRINTER_REMOTE = 0x0002 
     272CUPS_PRINTER_BW = 0x0004 
     273CUPS_PRINTER_COLOR = 0x0008 
     274CUPS_PRINTER_DUPLEX = 0x0010 
     275CUPS_PRINTER_STAPLE = 0x0020 
     276CUPS_PRINTER_COPIES = 0x0040 
     277CUPS_PRINTER_COLLATE = 0x0080 
     278CUPS_PRINTER_PUNCH = 0x0100 
     279CUPS_PRINTER_COVER = 0x0200 
     280CUPS_PRINTER_BIND = 0x0400 
     281CUPS_PRINTER_SORT = 0x0800 
     282CUPS_PRINTER_SMALL = 0x1000 
     283CUPS_PRINTER_MEDIUM = 0x2000 
     284CUPS_PRINTER_LARGE = 0x4000 
     285CUPS_PRINTER_VARIABLE = 0x8000 
     286CUPS_PRINTER_IMPLICIT = 0x1000 
     287CUPS_PRINTER_DEFAULT = 0x2000 
     288CUPS_PRINTER_FAX = 0x4000 
     289CUPS_PRINTER_REJECTING = 0x8000 
     290CUPS_PRINTER_DELETE = 0x1000 
     291CUPS_PRINTER_NOT_SHARED = 0x2000 
     292CUPS_PRINTER_AUTHENTICATED = 0x4000 
     293CUPS_PRINTER_COMMANDS = 0x8000 
     294CUPS_PRINTER_OPTIONS = 0xe6ff 
     295   
     296class FakeAttribute : 
     297    """Fakes an IPPRequest attribute to simplify usage syntax.""" 
     298    def __init__(self, request, name) : 
     299        """Initializes the fake attribute.""" 
     300        self.request = request 
     301        self.name = name 
     302         
     303    def __setitem__(self, key, value) : 
     304        """Appends the value to the real attribute.""" 
     305        attributeslist = getattr(self.request, "_%s_attributes" % self.name) 
     306        for i in range(len(attributeslist)) : 
     307            attribute = attributeslist[i] 
     308            for j in range(len(attribute)) : 
     309                (attrname, attrvalue) = attribute[j] 
     310                if attrname == key : 
     311                    attribute[j][1].append(value) 
     312                    return 
     313            attribute.append((key, [value]))         
     314             
     315    def __getitem__(self, key) : 
     316        """Returns an attribute's value.""" 
     317        answer = [] 
     318        attributeslist = getattr(self.request, "_%s_attributes" % self.name) 
     319        for i in range(len(attributeslist)) : 
     320            attribute = attributeslist[i] 
     321            for j in range(len(attribute)) : 
     322                (attrname, attrvalue) = attribute[j] 
     323                if attrname == key : 
     324                    answer.extend(attrvalue) 
     325        if answer : 
     326            return answer 
     327        raise KeyError, key             
     328     
    58329class IPPRequest : 
    59     """A class for IPP requests. 
    60      
    61        Usage : 
    62         
    63          fp = open("/var/spool/cups/c00001", "rb") 
    64          message = IPPRequest(fp.read()) 
    65          fp.close() 
    66          message.parse() 
    67          # print message.dump() # dumps an equivalent to the original IPP message 
    68          # print str(message)   # returns a string of text with the same content as below 
    69          print "IPP version : %s.%s" % message.version 
    70          print "IPP operation Id : 0x%04x" % message.operation_id 
    71          print "IPP request Id : 0x%08x" % message.request_id 
    72          for attrtype in message.attributes_types : 
    73              attrdict = getattr(message, "%s_attributes" % attrtype) 
    74              if attrdict : 
    75                  print "%s attributes :" % attrtype.title() 
    76                  for key in attrdict.keys() : 
    77                      print "  %s : %s" % (key, attrdict[key]) 
    78          if message.data :             
    79              print "IPP datas : ", repr(message.data)             
    80     """ 
     330    """A class for IPP requests.""" 
    81331    attributes_types = ("operation", "job", "printer", "unsupported", \ 
    82332                                     "subscription", "event_notification") 
    83     def __init__(self, data="", version=None, operation_id=None, \ 
    84                                               request_id=None, debug=0) : 
     333    def __init__(self, data="", version=IPP_VERSION,  
     334                                operation_id=None, \ 
     335                                request_id=None, \ 
     336                                debug=False) : 
    85337        """Initializes an IPP Message object. 
    86338         
     
    92344        self.debug = debug 
    93345        self._data = data 
    94         self.parsed = 0 
     346        self.parsed = False 
    95347         
    96348        # Initializes message 
    97         if version is not None : 
    98             try : 
    99                 self.version = [int(p) for p in version.split(".")] 
    100             except AttributeError : 
    101                 if len(version) == 2 : # 2-tuple 
    102                     self.version = version 
    103                 else :     
    104                     try : 
    105                         self.version = [int(p) for p in str(float(version)).split(".")] 
    106                     except : 
    107                         self.version = (1, 1) # default version number 
    108         self.operation_id = operation_id 
    109         self.request_id = request_id 
     349        self.setVersion(version)                 
     350        self.setOperationId(operation_id) 
     351        self.setRequestId(request_id) 
    110352        self.data = "" 
    111353         
    112         # Initialize attributes mappings 
    113354        for attrtype in self.attributes_types : 
    114             setattr(self, "%s_attributes" % attrtype, {}) 
    115              
     355            setattr(self, "_%s_attributes" % attrtype, [[]]) 
     356         
    116357        # Initialize tags     
    117358        self.tags = [ None ] * 256 # by default all tags reserved 
     
    164405         
    165406        # Reverse mapping to generate IPP messages 
    166         self.dictags = {} 
     407        self.tagvalues = {} 
    167408        for i in range(len(self.tags)) : 
    168409            value = self.tags[i] 
    169410            if value is not None : 
    170                 self.dictags[value] = i 
     411                self.tagvalues[value] = i 
     412                                      
     413    def __getattr__(self, name) :                                  
     414        """Fakes attribute access.""" 
     415        if name in self.attributes_types : 
     416            return FakeAttribute(self, name) 
     417        else : 
     418            raise AttributeError, name 
     419             
     420    def __str__(self) :         
     421        """Returns the parsed IPP message in a readable form.""" 
     422        if not self.parsed : 
     423            return "" 
     424        mybuffer = [] 
     425        mybuffer.append("IPP version : %s.%s" % self.version) 
     426        mybuffer.append("IPP operation Id : 0x%04x" % self.operation_id) 
     427        mybuffer.append("IPP request Id : 0x%08x" % self.request_id) 
     428        for attrtype in self.attributes_types : 
     429            for attribute in getattr(self, "_%s_attributes" % attrtype) : 
     430                if attribute : 
     431                    mybuffer.append("%s attributes :" % attrtype.title()) 
     432                for (name, value) in attribute : 
     433                    mybuffer.append("  %s : %s" % (name, value)) 
     434        if self.data :             
     435            mybuffer.append("IPP datas : %s" % repr(self.data)) 
     436        return "\n".join(mybuffer) 
    171437         
    172438    def logDebug(self, msg) :     
     
    176442            sys.stderr.flush() 
    177443             
    178     def __str__(self) :         
    179         """Returns the parsed IPP message in a readable form.""" 
    180         if not self.parsed : 
    181             return "" 
    182         else :     
    183             mybuffer = [] 
    184             mybuffer.append("IPP version : %s.%s" % self.version) 
    185             mybuffer.append("IPP operation Id : 0x%04x" % self.operation_id) 
    186             mybuffer.append("IPP request Id : 0x%08x" % self.request_id) 
    187             for attrtype in self.attributes_types : 
    188                 attrdict = getattr(self, "%s_attributes" % attrtype) 
    189                 if attrdict : 
    190                     mybuffer.append("%s attributes :" % attrtype.title()) 
    191                     for key in attrdict.keys() : 
    192                         mybuffer.append("  %s : %s" % (key, attrdict[key])) 
    193             if self.data :             
    194                 mybuffer.append("IPP datas : %s" % repr(self.data)) 
    195             return "\n".join(mybuffer) 
     444    def setVersion(self, version) : 
     445        """Sets the request's operation id.""" 
     446        if version is not None : 
     447            try : 
     448                self.version = [int(p) for p in version.split(".")] 
     449            except AttributeError : 
     450                if len(version) == 2 : # 2-tuple 
     451                    self.version = version 
     452                else :     
     453                    try : 
     454                        self.version = [int(p) for p in str(float(version)).split(".")] 
     455                    except : 
     456                        self.version = [int(p) for p in IPP_VERSION.split(".")] 
     457         
     458    def setOperationId(self, opid) :         
     459        """Sets the request's operation id.""" 
     460        self.operation_id = opid 
     461         
     462    def setRequestId(self, reqid) :         
     463        """Sets the request's request id.""" 
     464        self.request_id = reqid 
    196465         
    197466    def dump(self) :     
     
    201470        """     
    202471        mybuffer = [] 
    203         if None not in (self.version, self.operation_id, self.request_id) : 
     472        if None not in (self.version, self.operation_id) : 
    204473            mybuffer.append(chr(self.version[0]) + chr(self.version[1])) 
    205474            mybuffer.append(pack(">H", self.operation_id)) 
    206             mybuffer.append(pack(">I", self.request_id)) 
     475            mybuffer.append(pack(">I", self.request_id or 1)) 
    207476            for attrtype in self.attributes_types : 
    208                 tagprinted = 0 
    209                 for (attrname, value) in getattr(self, "%s_attributes" % attrtype).items() : 
    210                     if not tagprinted : 
    211                         mybuffer.append(chr(self.dictags["%s-attributes-tag" % attrtype])) 
    212                         tagprinted = 1 
    213                     if type(value) != type([]) : 
    214                         value = [ value ] 
    215                     for (vtype, val) in value : 
    216                         mybuffer.append(chr(self.dictags[vtype])) 
    217                         mybuffer.append(pack(">H", len(attrname))) 
    218                         mybuffer.append(attrname) 
    219                         if vtype in ("integer", "enum") : 
    220                             mybuffer.append(pack(">H", 4)) 
    221                             mybuffer.append(pack(">I", val)) 
    222                         elif vtype == "boolean" : 
    223                             mybuffer.append(pack(">H", 1)) 
    224                             mybuffer.append(chr(val)) 
    225                         else :     
    226                             mybuffer.append(pack(">H", len(val))) 
    227                             mybuffer.append(val) 
    228             mybuffer.append(chr(self.dictags["end-of-attributes-tag"])) 
     477                for attribute in getattr(self, "_%s_attributes" % attrtype) : 
     478                    if attribute : 
     479                        mybuffer.append(chr(self.tagvalues["%s-attributes-tag" % attrtype])) 
     480                    for (attrname, value) in attribute : 
     481                        nameprinted = 0 
     482                        for (vtype, val) in value : 
     483                            mybuffer.append(chr(self.tagvalues[vtype])) 
     484                            if not nameprinted : 
     485                                mybuffer.append(pack(">H", len(attrname))) 
     486                                mybuffer.append(attrname) 
     487                                nameprinted = 1 
     488                            else :      
     489                                mybuffer.append(pack(">H", 0)) 
     490                            if vtype in ("integer", "enum") : 
     491                                mybuffer.append(pack(">H", 4)) 
     492                                mybuffer.append(pack(">I", val)) 
     493                            elif vtype == "boolean" : 
     494                                mybuffer.append(pack(">H", 1)) 
     495                                mybuffer.append(chr(val)) 
     496                            else :     
     497                                mybuffer.append(pack(">H", len(val))) 
     498                                mybuffer.append(val) 
     499            mybuffer.append(chr(self.tagvalues["end-of-attributes-tag"])) 
    229500        mybuffer.append(self.data)     
    230501        return "".join(mybuffer) 
     
    236507        """ 
    237508        self._curname = None 
    238         self._curdict = None 
    239         self.version = (ord(self._data[0]), ord(self._data[1])) 
    240         self.operation_id = unpack(">H", self._data[2:4])[0] 
    241         self.request_id = unpack(">I", self._data[4:8])[0] 
     509        self._curattributes = None 
     510         
     511        self.setVersion((ord(self._data[0]), ord(self._data[1]))) 
     512        self.setOperationId(unpack(">H", self._data[2:4])[0]) 
     513        self.setRequestId(unpack(">I", self._data[4:8])[0]) 
    242514        self.position = 8 
    243         endofattributes = self.dictags["end-of-attributes-tag"] 
    244         maxdelimiter = self.dictags["event-notification-attributes-tag"] 
     515        endofattributes = self.tagvalues["end-of-attributes-tag"] 
     516        maxdelimiter = self.tagvalues["event_notification-attributes-tag"] 
     517        nulloffset = lambda : 0 
    245518        try : 
    246519            tag = ord(self._data[self.position]) 
     
    249522                name = self.tags[tag] 
    250523                if name is not None : 
    251                     func = getattr(self, name.replace("-", "_"), None) 
    252                     if func is not None : 
    253                         self.position += func() 
    254                         if ord(self._data[self.position]) > maxdelimiter : 
    255                             self.position -= 1 
    256                             continue 
     524                    func = getattr(self, name.replace("-", "_"), nulloffset) 
     525                    self.position += func() 
     526                    if ord(self._data[self.position]) > maxdelimiter : 
     527                        self.position -= 1 
     528                        continue 
     529                oldtag = tag         
    257530                tag = ord(self._data[self.position]) 
     531                if tag == oldtag : 
     532                    self._curattributes.append([]) 
    258533        except IndexError : 
    259534            raise IPPError, "Unexpected end of IPP message." 
    260535             
    261         # Now transform all one-element lists into single values 
    262         for attrtype in self.attributes_types : 
    263             attrdict = getattr(self, "%s_attributes" % attrtype) 
    264             for (key, value) in attrdict.items() : 
    265                 if len(value) == 1 : 
    266                     attrdict[key] = value[0] 
    267536        self.data = self._data[self.position+1:]             
    268         self.parsed = 1             
     537        self.parsed = True 
    269538         
    270539    def parseTag(self) :     
     
    288557        elif tagtype == "boolean" :     
    289558            value = ord(value) 
    290         oldval = self._curdict.setdefault(name, []) 
    291         oldval.append((tagtype, value)) 
     559        try :     
     560            (oldname, oldval) = self._curattributes[-1][-1] 
     561            if oldname == name : 
     562                oldval.append((tagtype, value)) 
     563            else :     
     564                raise IndexError 
     565        except IndexError :     
     566            self._curattributes[-1].append((name, [(tagtype, value)])) 
    292567        self.logDebug("%s(%s) : %s" % (name, tagtype, value)) 
    293568        return posend - self.position 
     
    295570    def operation_attributes_tag(self) :  
    296571        """Indicates that the parser enters into an operation-attributes-tag group.""" 
    297         self.logDebug("Start of operation_attributes_tag") 
    298         self._curdict = self.operation_attributes 
     572        self._curattributes = self._operation_attributes 
    299573        return self.parseTag() 
    300574         
    301575    def job_attributes_tag(self) :  
    302576        """Indicates that the parser enters into a job-attributes-tag group.""" 
    303         self.logDebug("Start of job_attributes_tag") 
    304         self._curdict = self.job_attributes 
     577        self._curattributes = self._job_attributes 
    305578        return self.parseTag() 
    306579         
    307580    def printer_attributes_tag(self) :  
    308581        """Indicates that the parser enters into a printer-attributes-tag group.""" 
    309         self.logDebug("Start of printer_attributes_tag") 
    310         self._curdict = self.printer_attributes 
     582        self._curattributes = self._printer_attributes 
    311583        return self.parseTag() 
    312584         
    313585    def unsupported_attributes_tag(self) :  
    314586        """Indicates that the parser enters into an unsupported-attributes-tag group.""" 
    315         self.logDebug("Start of unsupported_attributes_tag") 
    316         self._curdict = self.unsupported_attributes 
     587        self._curattributes = self._unsupported_attributes 
    317588        return self.parseTag() 
    318589         
    319590    def subscription_attributes_tag(self) :  
    320591        """Indicates that the parser enters into a subscription-attributes-tag group.""" 
    321         self.logDebug("Start of subscription_attributes_tag") 
    322         self._curdict = self.subscription_attributes 
     592        self._curattributes = self._subscription_attributes 
    323593        return self.parseTag() 
    324594         
    325595    def event_notification_attributes_tag(self) :  
    326596        """Indicates that the parser enters into an event-notification-attributes-tag group.""" 
    327         self.logDebug("Start of event_notification_attributes_tag") 
    328         self._curdict = self.event_notification_attributes 
     597        self._curattributes = self._event_notification_attributes 
    329598        return self.parseTag() 
     599         
     600             
     601class CUPS : 
     602    """A class for a CUPS instance.""" 
     603    def __init__(self, url=None, username=None, password=None, charset="utf-8", language="en-us", debug=False) : 
     604        """Initializes the CUPS instance.""" 
     605        if url is not None : 
     606            self.url = url.replace("ipp://", "http://") 
     607            if self.url.endswith("/") : 
     608                self.url = self.url[:-1] 
     609        else :         
     610            self.url = self.getDefaultURL() 
     611        self.username = username 
     612        self.password = password 
     613        self.charset = charset 
     614        self.language = language 
     615        self.debug = debug 
     616        self.lastError = None 
     617        self.lastErrorMessage = None 
     618        self.requestId = None 
     619         
     620    def getDefaultURL(self) :     
     621        """Builds a default URL.""" 
     622        # TODO : encryption methods. 
     623        server = os.environ.get("CUPS_SERVER") or "localhost" 
     624        port = os.environ.get("IPP_PORT") or 631 
     625        if server.startswith("/") : 
     626            # it seems it's a unix domain socket. 
     627            # we can't handle this right now, so we use the default instead. 
     628            return "http://localhost:%s" % port 
     629        else :     
     630            return "http://%s:%s" % (server, port) 
     631             
     632    def identifierToURI(self, service, ident) : 
     633        """Transforms an identifier into a particular URI depending on requested service.""" 
     634        return "%s/%s/%s" % (self.url.replace("http://", "ipp://"), 
     635                             service, 
     636                             ident) 
     637         
     638    def nextRequestId(self) :         
     639        """Increments the current request id and returns the new value.""" 
     640        try : 
     641            self.requestId += 1 
     642        except TypeError :     
     643            self.requestId = 1 
     644        return self.requestId 
     645             
     646    def newRequest(self, operationid=None) : 
     647        """Generates a new empty request.""" 
     648        if operationid is not None : 
     649            req = IPPRequest(operation_id=operationid, \ 
     650                             request_id=self.nextRequestId(), \ 
     651                             debug=self.debug) 
     652            req.operation["attributes-charset"] = ("charset", self.charset) 
     653            req.operation["attributes-natural-language"] = ("naturalLanguage", self.language) 
     654            return req 
     655     
     656    def doRequest(self, req, url=None) : 
     657        """Sends a request to the CUPS server. 
     658           returns a new IPPRequest object, containing the parsed answer. 
     659        """    
     660        connexion = urllib2.Request(url=url or self.url, \ 
     661                             data=req.dump()) 
     662        connexion.add_header("Content-Type", "application/ipp") 
     663        if self.username : 
     664            pwmanager = urllib2.HTTPPasswordMgrWithDefaultRealm() 
     665            pwmanager.add_password(None, \ 
     666                                   "%s%s" % (connexion.get_host(), connexion.get_selector()), \ 
     667                                   self.username, \ 
     668                                   self.password or "") 
     669            authhandler = urllib2.HTTPBasicAuthHandler(pwmanager)                        
     670            opener = urllib2.build_opener(authhandler) 
     671            urllib2.install_opener(opener) 
     672        self.lastError = None     
     673        self.lastErrorMessage = None 
     674        try :     
     675            response = urllib2.urlopen(connexion) 
     676        except (urllib2.URLError, urllib2.HTTPError, socket.error), error :     
     677            self.lastError = error 
     678            self.lastErrorMessage = str(error) 
     679            return None 
     680        else :     
     681            datas = response.read() 
     682            ippresponse = IPPRequest(datas) 
     683            ippresponse.parse() 
     684            return ippresponse 
     685     
     686    def getPPD(self, queuename) :     
     687        """Retrieves the PPD for a particular queuename.""" 
     688        req = self.newRequest(IPP_GET_PRINTER_ATTRIBUTES) 
     689        req.operation["printer-uri"] = ("uri", self.identifierToURI("printers", queuename)) 
     690        for attrib in ("printer-uri-supported", "printer-type", "member-uris") : 
     691            req.operation["requested-attributes"] = ("nameWithoutLanguage", attrib) 
     692        return self.doRequest(req)  # TODO : get the PPD from the actual print server 
     693         
     694    def getDefault(self) : 
     695        """Retrieves CUPS' default printer.""" 
     696        return self.doRequest(self.newRequest(CUPS_GET_DEFAULT)) 
     697     
     698    def getJobAttributes(self, jobid) :     
     699        """Retrieves a print job's attributes.""" 
     700        req = self.newRequest(IPP_GET_JOB_ATTRIBUTES) 
     701        req.operation["job-uri"] = ("uri", self.identifierToURI("jobs", jobid)) 
     702        return self.doRequest(req) 
     703         
     704    def getPrinters(self) :     
     705        """Returns the list of print queues names.""" 
     706        req = self.newRequest(CUPS_GET_PRINTERS) 
     707        req.operation["requested-attributes"] = ("keyword", "printer-name") 
     708        req.operation["printer-type"] = ("enum", 0) 
     709        req.operation["printer-type-mask"] = ("enum", CUPS_PRINTER_CLASS) 
     710        return [printer[1] for printer in self.doRequest(req).printer["printer-name"]] 
     711         
     712    def getDevices(self) :     
     713        """Returns a list of devices as (deviceclass, deviceinfo, devicemakeandmodel, deviceuri) tuples.""" 
     714        answer = self.doRequest(self.newRequest(CUPS_GET_DEVICES)) 
     715        return zip([d[1] for d in answer.printer["device-class"]], \ 
     716                   [d[1] for d in answer.printer["device-info"]], \ 
     717                   [d[1] for d in answer.printer["device-make-and-model"]], \ 
     718                   [d[1] for d in answer.printer["device-uri"]]) 
     719                    
     720    def getPPDs(self) :     
     721        """Returns a list of PPDs as (ppdnaturallanguage, ppdmake, ppdmakeandmodel, ppdname) tuples.""" 
     722        answer = self.doRequest(self.newRequest(CUPS_GET_PPDS)) 
     723        return zip([d[1] for d in answer.printer["ppd-natural-language"]], \ 
     724                   [d[1] for d in answer.printer["ppd-make"]], \ 
     725                   [d[1] for d in answer.printer["ppd-make-and-model"]], \ 
     726                   [d[1] for d in answer.printer["ppd-name"]]) 
     727                    
     728    def createSubscription(self, uri, events=["all"], 
     729                                      userdata=None, 
     730                                      recipient=None, 
     731                                      pullmethod=None, 
     732                                      charset=None, 
     733                                      naturallanguage=None, 
     734                                      leaseduration=None, 
     735                                      timeinterval=None, 
     736                                      jobid=None) : 
     737        """Creates a job, printer or server subscription. 
     738          
     739           uri : the subscription's uri, e.g. ipp://server 
     740           events : a list of events to subscribe to, e.g. ["printer-added", "printer-deleted"] 
     741           recipient : the notifier's uri 
     742           pullmethod : the pull method to use 
     743           charset : the charset to use when sending notifications 
     744           naturallanguage : the language to use when sending notifications 
     745           leaseduration : the duration of the lease in seconds 
     746           timeinterval : the interval of time during notifications 
     747           jobid : the optional job id in case of a job subscription 
     748        """    
     749        if jobid is not None : 
     750            opid = IPP_CREATE_JOB_SUBSCRIPTION 
     751            uritype = "job-uri" 
     752        else : 
     753            opid = IPP_CREATE_PRINTER_SUBSCRIPTION 
     754            uritype = "printer-uri" 
     755        req = self.newRequest(opid) 
     756        req.operation[uritype] = ("uri", uri) 
     757        for event in events : 
     758            req.subscription["notify-events"] = ("keyword", event) 
     759        if userdata is not None :     
     760            req.subscription["notify-user-data"] = ("octetString-with-an-unspecified-format", userdata) 
     761        if recipient is not None :     
     762            req.subscription["notify-recipient"] = ("uri", recipient) 
     763        if pullmethod is not None : 
     764            req.subscription["notify-pull-method"] = ("keyword", pullmethod) 
     765        if charset is not None : 
     766            req.subscription["notify-charset"] = ("charset", charset) 
     767        if naturallanguage is not None : 
     768            req.subscription["notify-natural-language"] = ("naturalLanguage", naturallanguage) 
     769        if leaseduration is not None : 
     770            req.subscription["notify-lease-duration"] = ("integer", leaseduration) 
     771        if timeinterval is not None : 
     772            req.subscription["notify-time-interval"] = ("integer", timeinterval) 
     773        if jobid is not None : 
     774            req.subscription["notify-job-id"] = ("integer", jobid) 
     775        return self.doRequest(req) 
     776             
     777    def cancelSubscription(self, uri, subscriptionid, jobid=None) :     
     778        """Cancels a subscription. 
     779         
     780           uri : the subscription's uri. 
     781           subscriptionid : the subscription's id. 
     782           jobid : the optional job's id. 
     783        """ 
     784        req = self.newRequest(IPP_CANCEL_SUBSCRIPTION) 
     785        if jobid is not None : 
     786            uritype = "job-uri" 
     787        else : 
     788            uritype = "printer-uri" 
     789        req.operation[uritype] = ("uri", uri) 
     790        req.event_notification["notify-subscription-id"] = ("integer", subscriptionid) 
     791        return self.doRequest(req) 
     792         
    330793 
    331794class FakeConfig : 
     
    498961    def initBackend(self) : 
    499962        """Initializes the backend's attributes.""" 
     963        self.JobId = sys.argv[1].strip() 
     964        self.UserName = sys.argv[2].strip() or pwd.getpwuid(os.geteuid())[0] # use CUPS' user when printing test pages from CUPS' web interface 
     965        self.Title = sys.argv[3].strip() 
     966        self.Copies = int(sys.argv[4].strip()) 
     967        self.Options = sys.argv[5].strip() 
     968        if len(sys.argv) == 7 : 
     969            self.InputFile = sys.argv[6] # read job's datas from file 
     970        else : 
     971            self.InputFile = None        # read job's datas from stdin 
     972        self.PrinterName = os.environ.get("PRINTER", "") 
     973        self.Directory = self.getPrintQueueOption(self.PrinterName, "directory", ignore=1) or tempfile.gettempdir() 
     974        self.DataFile = os.path.join(self.Directory, "%s-%s-%s-%s" % (self.myname, self.PrinterName, self.UserName, self.JobId)) 
     975         
    500976        # check that the DEVICE_URI environment variable's value is 
    501977        # prefixed with self.myname otherwise don't touch it. 
     
    519995                raise TeeError, "Invalid DEVICE_URI : %s\n" % device_uri 
    520996 
    521         self.JobId = sys.argv[1].strip() 
    522         self.UserName = sys.argv[2].strip() or pwd.getpwuid(os.geteuid())[0] # use CUPS' user when printing test pages from CUPS' web interface 
    523         self.Title = sys.argv[3].strip() 
    524         self.Copies = int(sys.argv[4].strip()) 
    525         self.Options = sys.argv[5].strip() 
    526         if len(sys.argv) == 7 : 
    527             self.InputFile = sys.argv[6] # read job's datas from file 
    528         else : 
    529             self.InputFile = None        # read job's datas from stdin 
    530  
    531997        self.RealBackend = backend 
    532998        self.DeviceURI = device_uri 
    533         self.PrinterName = os.environ.get("PRINTER", "") 
    534         self.Directory = self.getPrintQueueOption(self.PrinterName, "directory", ignore=1) or tempfile.gettempdir() 
    535         self.DataFile = os.path.join(self.Directory, "%s-%s-%s-%s" % (self.myname, self.PrinterName, self.UserName, self.JobId)) 
    536         (ippfilename, ippmessage) = self.parseIPPRequestFile() 
    537         self.ControlFile = ippfilename 
    538         john = ippmessage.operation_attributes.get("job-originating-host-name", \ 
    539                ippmessage.job_attributes.get("job-originating-host-name", \ 
    540                (None, None))) 
     999         
     1000        try : 
     1001            cupsserver = CUPS() # TODO : username and password and/or encryption 
     1002            answer = cupsserver.getJobAttributes(self.JobId) 
     1003            self.ControlFile = "NotUsedAnymore" 
     1004        except :     
     1005            (ippfilename, answer) = self.parseIPPRequestFile() 
     1006            self.ControlFile = ippfilename 
     1007         
     1008        try : 
     1009            john = answer.job["job-originating-host-name"] 
     1010        except KeyError :     
     1011            try : 
     1012                john = answer.operation["job-originating-host-name"] 
     1013            except KeyError :     
     1014                john = (None, None) 
    5411015        if type(john) == type([]) :                           
    5421016            john = john[-1] 
    5431017        (chtype, self.ClientHost) = john                           
    544         (jbtype, self.JobBilling) = ippmessage.job_attributes.get("job-billing", (None, None)) 
     1018        try :         
     1019            jbing = answer.job["job-billing"] 
     1020        except KeyError :     
     1021            jbing = (None, None) 
     1022        if type(jbing) == type([]) :  
     1023            jbing = jbing[-1] 
     1024        (jbtype, self.JobBilling) = jbing 
    5451025 
    5461026    def getCupsConfigDirectives(self, directives=[]) : 
     
    5731053    def parseIPPRequestFile(self) : 
    5741054        """Parses the IPP message file and returns a tuple (filename, parsedvalue).""" 
    575         cupsdconf = self.getCupsConfigDirectives(["RequestRoot"]) 
    576         requestroot = cupsdconf.get("requestroot", "/var/spool/cups") 
     1055        requestroot = os.environ.get("CUPS_REQUESTROOT") 
     1056        if requestroot is None : 
     1057            cupsdconf = self.getCupsConfigDirectives(["RequestRoot"]) 
     1058            requestroot = cupsdconf.get("requestroot", "/var/spool/cups") 
    5771059        if (len(self.JobId) < 5) and self.JobId.isdigit() : 
    5781060            ippmessagefile = "c%05i" % int(self.JobId) 
     
    8271309            else : 
    8281310                if (not number) or (loopcount < number) : 
    829                     self.logInfo(_("The real backend produced an error, we will try again in %s seconds.") % delay, "warn") 
     1311                    self.logInfo("The real backend produced an error, we will try again in %s seconds." % delay, "warn") 
    8301312                    time.sleep(delay) 
    8311313                    loopcount += 1