source: server/lib/gutenbach/server/requests.py @ b77e57e

no-cups
Last change on this file since b77e57e was b77e57e, checked in by Daniel Cooper <danny@…>, 12 years ago

Merge branch 'no-cups' of github.com:jhamrick/gutenbach into no-cups

Conflicts:

server/lib/gutenbach/server/requests.py

  • Property mode set to 100644
File size: 59.3 KB
Line 
1from . import InvalidJobException, InvalidPrinterStateException, InvalidJobStateException
2import gutenbach.ipp as ipp
3import logging
4import traceback
5import sys
6
7# initialize logger
8logger = logging.getLogger(__name__)
9
10def handler_for(operation):
11    """A decorator method to mark a function with the operation id
12    that it handles.  This value will be stored in
13    'func.ipp_operation'.
14
15    """
16   
17    def f(func):
18        func.ipp_operation = operation
19        return func
20    return f
21
22def make_empty_response(request):
23    # Operation attributes -- typically the same for any request
24    attribute_group = ipp.AttributeGroup(
25        ipp.AttributeTags.OPERATION,
26        [ipp.AttributesCharset('utf-8'),
27         ipp.AttributesNaturalLanguage('en-us')])
28   
29    # Set up the default response -- handlers will override these
30    # values if they need to
31    response_kwargs = {}
32    response_kwargs['version']          = request.version
33    response_kwargs['operation_id']     = ipp.StatusCodes.OK
34    response_kwargs['request_id']       = request.request_id
35    response_kwargs['attribute_groups'] = [attribute_group]
36    response = ipp.Request(**response_kwargs)
37   
38    return response
39
40def verify_attribute(attr, cls, length=1):
41    vals = [val.value for val in attr.values]
42    if attr != cls(*vals):
43        raise ipp.errors.ClientErrorBadRequest(str(attr))
44    if length is not None and len(vals) != length:
45        raise ipp.errors.ClientErrorBadRequest(str(attr))
46    return vals
47
48class GutenbachRequestHandler(object):
49
50    def __init__(self, gutenbach_printer):
51        self.printer = gutenbach_printer
52
53    def generic_handle(self, request):
54        # check the IPP version number
55        if request.version != (1, 1):
56            raise ipp.errors.ServerErrorVersionNotSupported(str(request.version))
57
58        # make sure the operation attribute group has the correct tag
59        operation = request.attribute_groups[0]
60        if operation.tag != ipp.AttributeTags.OPERATION:
61            raise ipp.errors.ClientErrorBadRequest(
62                "Attribute group does not have OPERATION tag: 0x%x" % operation.tag)
63
64        # check charset
65        charset_attr = operation.attributes[0]
66        charset = verify_attribute(charset_attr, ipp.AttributesCharset)[0]
67        if charset != 'utf-8':
68            raise ipp.errors.ClientErrorAttributes(str(charset_attr))
69
70        # check for attributes-natural-language
71        natlang_attr = operation.attributes[1]
72        natlang = verify_attribute(natlang_attr, ipp.AttributesNaturalLanguage)[0]
73        if natlang != 'en-us':
74            raise ipp.errors.ClientErrorAttributes(str(natlang_attr))
75   
76    def handle(self, request):
77        # look up the handler
78        handler = None
79        handler_name = None
80        for d in dir(self):
81            if getattr(getattr(self, d), "ipp_operation", None) == request.operation_id:
82                handler_name = d
83                break
84
85        # we couldn't find a handler, so default to unknown operation
86        if handler_name is None:
87            handler_name = "unknown_operation"
88
89        # actually get the handler
90        handler = getattr(self, handler_name)
91        logger.info("request is '%s'" % handler_name)
92
93        # try to handle the request
94        try:
95            self.generic_handle(request)
96            response = make_empty_response(request)
97            handler(request, response)
98
99        # Handle any errors that occur.  If an exception occurs that
100        # is an IPP error, then we can get the error code from the
101        # exception itself.
102        except ipp.errors.IPPException:
103            exctype, excval, exctb = sys.exc_info()
104            logger.error("%s: %s" % (exctype.__name__, excval.message))
105            response = make_empty_response(request)
106            excval.update_response(response)
107
108        # If it wasn't an IPP error, then it's our fault, so mark it
109        # as an internal server error
110        except Exception:
111            logger.error(traceback.format_exc())
112            response = make_empty_response(request)
113            response.operation_id = ipp.StatusCodes.INTERNAL_ERROR
114
115        return response
116
117    def unknown_operation(self, request, response):
118        logger.warning("unknown operation 0x%x" % request.operation_id)
119        response = make_empty_response(request)
120        response.operation_id = ipp.StatusCodes.OPERATION_NOT_SUPPORTED
121        return response
122       
123    ##### Printer Commands
124
125    @handler_for(ipp.OperationCodes.PRINT_JOB)
126    def print_job(self, request, response):
127        """RFC 2911: 3.2.1 Print-Job Operation
128
129        This REQUIRED operation allows a client to submit a print job
130        with only one document and supply the document data (rather
131        than just a reference to the data). See Section 15 for the
132        suggested steps for processing create operations and their
133        Operation and Job Template attributes.
134
135        Request
136        -------
137        Group 1: Operation Attributes
138            REQUIRED 'attributes-charset'
139            REQUIRED 'attributes-natural-language'
140            REQUIRED 'printer-uri' (uri)
141            OPTIONAL 'requesting-user-name' (name(MAX))
142            OPTIONAL 'job-name' (name(MAX))
143            OPTIONAL 'ipp-attribute-fidelity' (boolean)
144            OPTIONAL 'document-name' (name(MAX))
145            OPTIONAL 'compression' (type3 keyword)
146            OPTIONAL 'document-format' (mimeMediaType)
147            OPTIONAL 'document-natural-language' (naturalLanguage)
148            OPTIONAL 'job-k-octets' (integer(0:MAX))
149            OPTIONAL 'job-impressions' (integer(0:MAX))
150            OPTIONAL 'job-media-sheets' (integer(0:MAX))
151        Group 2: Job Template Attributes
152        Group 3: Document Content
153
154        Response
155        --------
156        Group 1: Operation Attributes
157            OPTIONAL 'status-message' (text(255))
158            OPTIONAL 'detailed-status-message' (text(MAX))
159            REQUIRED 'attributes-charset'
160            REQUIRED 'attributes-natural-language'
161        Group 2: Unsupported Attributes
162        Group 3: Job Object Attributes
163            REQUIRED 'job-uri' (uri)
164            REQUIRED 'job-id' (integer(1:MAX))
165            REQUIRED 'job-state' (type1 enum)
166            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
167            OPTIONAL 'job-state-message' (text(MAX))
168            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
169
170        """
171
172        operation = request.attribute_groups[0]
173        document = request.data       
174        user_name = None
175        job_name = None
176        job_k_octets = None
177        document_format = None
178        document_natural_language = None
179        compression = None
180        last_document = None
181        # requested printer uri
182        if 'printer-uri' not in operation:
183            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
184        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
185        if printer_uri not in self.printer.uris:
186            raise ipp.errors.ClientErrorAttributes(
187                str(operation['printer-uri']), operation['printer-uri'])
188
189        if 'requesting-user-name' in operation:
190            user_name = verify_attribute(
191                operation['requesting-user-name'], ipp.RequestingUserName)[0]
192
193        if 'job-name' in operation:
194            job_name = verify_attribute(
195                operation['job-name'], ipp.JobName)[0]
196
197        if 'job-k-octets' in operation:
198            job_k_octets = verify_attribute(
199                operation['job-k-octets'], ipp.JobKOctets)[0]
200
201        if 'ipp-attribute-fidelity' in operation:
202            pass # don't care
203        if 'job-impressions' in operation:
204            pass # don't care
205        if 'job-media-sheets' in operation:
206            pass # don't care
207
208        # get attributes from the printer and add to response
209        try:
210            job_id = self.printer.print_job(document,
211                    document_name               = document_name,
212                    document_format             = document_format,
213                    document_natural_language   = document_natural_language,
214                    requesting_user_name        = user_name,
215                    compression                 = compression,
216                    job_name                    = job_name,
217                    job_k_octets                = job_k_octets)
218        except InvalidJobException:
219            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
220
221
222        attrs = self.printer.get_job_attributes(job_id)
223     
224        #Actually append the attributes we pulled
225        response.attribute_groups.append(ipp.AttributeGroup(
226            ipp.AttributeTags.JOB, attrs))
227
228       
229    @handler_for(ipp.OperationCodes.VALIDATE_JOB)
230    def validate_job(self, request, response):
231        """3.2.3 Validate-Job Operation
232
233        This REQUIRED operation is similar to the Print-Job operation
234        (section 3.2.1) except that a client supplies no document data and
235        the Printer allocates no resources (i.e., it does not create a new
236        Job object).  This operation is used only to verify capabilities of a
237        printer object against whatever attributes are supplied by the client
238        in the Validate-Job request.  By using the Validate-Job operation a
239        client can validate that an identical Print-Job operation (with the
240        document data) would be accepted. The Validate-Job operation also
241        performs the same security negotiation as the Print-Job operation
242        (see section 8), so that a client can check that the client and
243        Printer object security requirements can be met before performing a
244        Print-Job operation.
245
246        The Validate-Job operation does not accept a 'document-uri' attribute
247        in order to allow a client to check that the same Print-URI operation
248        will be accepted, since the client doesn't send the data with the
249        Print-URI operation.  The client SHOULD just issue the Print-URI
250        request.
251
252        The Printer object returns the same status codes, Operation
253        Attributes (Group 1) and Unsupported Attributes (Group 2) as the
254        Print-Job operation.  However, no Job Object Attributes (Group 3) are
255        returned, since no Job object is created.
256        """
257        operation = request.attribute_groups[0]
258        user_name = None
259        job_name = None
260        job_k_octets = None
261        # requested printer uri
262        if 'printer-uri' not in operation:
263            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
264        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
265        if printer_uri not in self.printer.uris:
266            raise ipp.errors.ClientErrorAttributes(
267                str(operation['printer-uri']), operation['printer-uri'])
268
269        if 'requesting-user-name' in operation:
270            user_name = verify_attribute(
271                operation['requesting-user-name'], ipp.RequestingUserName)[0]
272
273        if 'job-name' in operation:
274            job_name = verify_attribute(
275                operation['job-name'], ipp.JobName)[0]
276
277        if 'job-k-octets' in operation:
278            job_k_octets = verify_attribute(
279                operation['job-k-octets'], ipp.JobKOctets)[0]
280       
281        self.printer.verify_job(requesting_user_name=user_name,
282            job_name=job_name,
283            job_k_octets = job_k_octets) 
284
285        #raise ipp.errors.ServerErrorOperationNotSupported
286
287    @handler_for(ipp.OperationCodes.GET_JOBS)
288    def get_jobs(self, request, response):
289        """3.2.6 Get-Jobs Operation
290       
291        This REQUIRED operation allows a client to retrieve the list
292        of Job objects belonging to the target Printer object. The
293        client may also supply a list of Job attribute names and/or
294        attribute group names. A group of Job object attributes will
295        be returned for each Job object that is returned.
296
297        This operation is similar to the Get-Job-Attributes operation,
298        except that this Get-Jobs operation returns attributes from
299        possibly more than one object.
300
301        Request
302        -------
303        Group 1: Operation Attributes
304            REQUIRED 'attributes-charset'
305            REQUIRED 'attributes-natural-language'
306            REQUIRED 'printer-uri' (uri)
307            OPTIONAL 'requesting-user-name' (name(MAX))
308            OPTIONAL 'limit' (integer(1:MAX))
309            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
310            OPTIONAL 'which-jobs' (type2 keyword)
311            OPTIONAL 'my-jobs' (boolean)
312
313        Response
314        --------
315        Group 1: Operation Attributes
316            OPTIONAL 'status-message' (text(255))
317            OPTIONAL 'detailed-status-message' (text(MAX))
318            REQUIRED 'attributes-charset'
319            REQUIRED 'attributes-natural-language'
320        Group 2: Unsupported Attributes
321        Groups 3 to N: Job Object Attributes
322
323        """
324
325        operation = request.attribute_groups[0]
326
327        # initialize operation attribute variables
328        printer_name = None
329        user = None
330        limit = None
331        attributes = None
332        which_jobs = None
333        my_jobs = None
334
335        # requested printer uri
336        if 'printer-uri' not in operation:
337            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
338        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
339        if printer_uri not in self.printer.uris and printer_uri != "ipp://localhost/":
340            raise ipp.errors.ClientErrorAttributes(
341                str(operation['printer-uri']), operation['printer-uri'])
342
343        # optional attributes
344        if 'limit' in operation:
345            limit = verify_attribute(
346                operation['limit'], ipp.Limit)[0]
347           
348        if 'requested-attributes' in operation:
349            attributes = verify_attribute(
350                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
351           
352        if 'which-jobs' in operation:
353            which_jobs = verify_attribute(
354                operation['which-jobs'], ipp.WhichJobs)[0]
355           
356        if 'my-jobs' in operation:
357            my_jobs = verify_attribute(
358                operation['my-jobs'], ipp.MyJobs)[0]
359
360        if 'requesting-user-name' in operation:
361            user = verify_attribute(
362                operation['requesting-user-name'], ipp.RequestingUserName)[0]
363            # ignore if we're not filtering jobs by user
364            if not my_jobs:
365                user = None
366           
367        # get the job attributes and add them to the response
368        job_attrs = self.printer.get_jobs(
369            which_jobs=which_jobs,
370            requesting_user_name=user,
371            requested_attributes=attributes)
372        for attrs in job_attrs:
373            response.attribute_groups.append(ipp.AttributeGroup(
374                ipp.AttributeTags.JOB, attrs))
375
376    @handler_for(ipp.OperationCodes.PRINT_URI)
377    def print_uri(self, request, response):
378        """3.2.2 Print-URI Operation
379
380        This OPTIONAL operation is identical to the Print-Job operation
381        (section 3.2.1) except that a client supplies a URI reference to the
382        document data using the 'document-uri' (uri) operation attribute (in
383        Group 1) rather than including the document data itself.  Before
384        returning the response, the Printer MUST validate that the Printer
385        supports the retrieval method (e.g., http, ftp, etc.) implied by the
386        URI, and MUST check for valid URI syntax.  If the client-supplied URI
387        scheme is not supported, i.e. the value is not in the Printer
388        object's 'referenced-uri-scheme-supported' attribute, the Printer
389        object MUST reject the request and return the 'client-error-uri-
390        scheme-not-supported' status code.
391
392        The IPP Printer MAY validate the accessibility of the document as
393        part of the operation or subsequently.  If the Printer determines an
394        accessibility problem before returning an operation response, it
395        rejects the request and returns the 'client-error-document-access-
396        error' status code.  The Printer MAY also return a specific document
397        access error code using the 'document-access-error' operation
398        attribute (see section 3.1.6.4).
399
400        If the Printer determines this document accessibility problem after
401        accepting the request and returning an operation response with one
402        of the successful status codes, the Printer adds the
403        'document-access- error' value to the job's 'job-state-reasons'
404        attribute and MAY populate the job's 'job-document-access-errors'
405        Job Description attribute (see section 4.3.11).  See The
406        Implementer's Guide [IPP- IIG] for suggested additional checks.
407                                                                             
408        If the Printer object supports this operation, it MUST support the
409        'reference-uri-schemes-supported' Printer attribute (see section 4.4.27).
410
411        It is up to the IPP object to interpret the URI and subsequently
412        'pull' the document from the source referenced by the URI string."""
413        operation = request.attribute_groups[0]
414        document = request.data       
415        user_name = None
416        job_name = None
417        job_k_octets = None
418        document_format = None
419        document_natural_language = None
420        compression = None
421        last_document = None
422
423
424        # requested printer uri
425        if 'printer-uri' not in operation:
426            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
427        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
428        if printer_uri not in self.printer.uris:
429            raise ipp.errors.ClientErrorAttributes(
430                str(operation['printer-uri']), operation['printer-uri'])
431
432        if 'requesting-user-name' in operation:
433            user_name = verify_attribute(
434                operation['requesting-user-name'], ipp.RequestingUserName)[0]
435
436        if 'job-name' in operation:
437            job_name = verify_attribute(
438                operation['job-name'], ipp.JobName)[0]
439
440        if 'job-k-octets' in operation:
441            job_k_octets = verify_attribute(
442                operation['job-k-octets'], ipp.JobKOctets)[0]
443
444        if 'ipp-attribute-fidelity' in operation:
445            pass # don't care
446        if 'job-impressions' in operation:
447            pass # don't care
448        if 'job-media-sheets' in operation:
449            pass # don't care
450
451        # get attributes from the printer and add to response
452        try:
453            job_id = self.printer.print_uri(document,
454                    document_name               = document_name,
455                    document_format             = document_format,
456                    document_natural_language   = document_natural_language,
457                    requesting_user_name        = user_name,
458                    compression                 = compression,
459                    job_name                    = job_name,
460                    job_k_octets                = job_k_octets)
461        except InvalidJobException:
462            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
463
464
465        attrs = self.printer.get_job_attributes(job_id)
466     
467        #Actually append the attributes we pulled
468        response.attribute_groups.append(ipp.AttributeGroup(
469            ipp.AttributeTags.JOB, attrs))
470
471 
472       #raise ipp.errors.ServerErrorOperationNotSupported
473
474    @handler_for(ipp.OperationCodes.CREATE_JOB)
475    def create_job(self, request, response):
476        """RFC 2911: 3.2.4 Create-Job Operation
477
478        This OPTIONAL operation is similar to the Print-Job operation
479        (section 3.2.1) except that in the Create-Job request, a
480        client does not supply document data or any reference to
481        document data. Also, the client does not supply any of the
482        'document-name', 'document- format', 'compression', or
483        'document-natural-language' operation attributes. This
484        operation is followed by one or more Send-Document or Send-URI
485        operations. In each of those operation requests, the client
486        OPTIONALLY supplies the 'document-name', 'document-format',
487        and 'document-natural-language' attributes for each document
488        in the multi-document Job object.
489
490        Request
491        -------
492        Group 1: Operation Attributes
493            REQUIRED 'attributes-charset'
494            REQUIRED 'attributes-natural-language'
495            REQUIRED 'printer-uri' (uri)
496            OPTIONAL 'requesting-user-name' (name(MAX))
497            OPTIONAL 'job-name' (name(MAX))
498            OPTIONAL 'ipp-attribute-fidelity' (boolean)
499            OPTIONAL 'job-k-octets' (integer(0:MAX))
500            OPTIONAL 'job-impressions' (integer(0:MAX))
501            OPTIONAL 'job-media-sheets' (integer(0:MAX))
502        Group 2: Job Template Attributes
503
504        Response
505        --------
506        Group 1: Operation Attributes
507            OPTIONAL 'status-message' (text(255))
508            OPTIONAL 'detailed-status-message' (text(MAX))
509            REQUIRED 'attributes-charset'
510            REQUIRED 'attributes-natural-language'
511        Group 2: Unsupported Attributes
512        Group 3: Job Object Attributes
513            REQUIRED 'job-uri' (uri)
514            REQUIRED 'job-id' (integer(1:MAX))
515            REQUIRED 'job-state' (type1 enum)
516            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
517            OPTIONAL 'job-state-message' (text(MAX))
518            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
519       
520        """
521
522        operation = request.attribute_groups[0]
523
524        printer_uri = None
525        requesting_user_name = None
526        job_name = None
527        ipp_attribute_fidelity=None
528        job_k_octets = None
529
530        # requested printer uri
531        if 'printer-uri' not in operation:
532            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
533        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
534        if printer_uri not in self.printer.uris:
535            raise ipp.errors.ClientErrorAttributes(
536                str(operation['printer-uri']), operation['printer-uri'])
537
538        if 'requesting-user-name' in operation:
539            user_name = verify_attribute(
540                operation['requesting-user-name'], ipp.RequestingUserName)[0]
541
542        if 'job-name' in operation:
543            job_name = verify_attribute(
544                operation['job-name'], ipp.JobName)[0]
545
546        if 'job-k-octets' in operation:
547            job_k_octets = verify_attribute(
548                operation['job-k-octets'], ipp.JobKOctets)[0]
549
550        if 'ipp-attribute-fidelity' in operation:
551            pass # don't care
552        if 'job-impressions' in operation:
553            pass # don't care
554        if 'job-media-sheets' in operation:
555            pass # don't care
556
557        # get attributes from the printer and add to response
558        job_id = self.printer.create_job(
559            requesting_user_name=requesting_user_name,
560            job_name=job_name,
561            job_k_octets=job_k_octets)
562        attrs = self.printer.get_job_attributes(job_id)
563        response.attribute_groups.append(ipp.AttributeGroup(
564            ipp.AttributeTags.JOB, attrs))
565   
566    @handler_for(ipp.OperationCodes.PAUSE_PRINTER)
567    def pause_printer(self, request, response):
568        """
569            3.2.7 Pause-Printer Operation
570
571            This OPTIONAL operation allows a client to stop the Printer object
572            from scheduling jobs on all its devices.  Depending on
573            implementation, the Pause-Printer operation MAY also stop the Printer
574            from processing the current job or jobs.  Any job that is currently
575            being printed is either stopped as soon as the implementation permits
576            or is completed, depending on implementation.  The Printer object
577            MUST still accept create operations to create new jobs, but MUST
578            prevent any jobs from entering the 'processing' state.
579
580            If the Pause-Printer operation is supported, then the Resume-Printer
581            operation MUST be supported, and vice-versa.
582
583            The IPP Printer stops the current job(s) on its device(s) that were
584            in the 'processing' or 'processing-stopped' states as soon as the
585            implementation permits.  If the implementation will take appreciable
586            time to stop, the IPP Printer adds the 'moving-to-paused' value to
587            the Printer object's 'printer-state-reasons' attribute (see section
588            4.4.12).  When the device(s) have all stopped, the IPP Printer
589            transitions the Printer object to the 'stopped' state, removes the
590            'moving-to-paused' value, if present, and adds the 'paused' value to
591            the Printer object's 'printer-state-reasons' attribute.
592
593            When the current job(s) complete that were in the 'processing' state,
594            the IPP Printer transitions them to the 'completed' state.  When the
595            current job(s) stop in mid processing that were in the 'processing'
596            state, the IPP Printer transitions them to the 'processing-stopped'
597            state and adds the 'printer-stopped' value to the job's 'job-state-
598            reasons' attribute.
599
600            For any jobs that are 'pending' or 'pending-held', the 'printer-
601            stopped' value of the jobs' 'job-state-reasons' attribute also
602            applies.  However, the IPP Printer NEED NOT update those jobs' 'job-
603            state-reasons' attributes and only need return the 'printer-stopped'
604            value when those jobs are queried (so-called 'lazy evaluation').
605
606            Whether the Pause-Printer operation affects jobs that were submitted
607            to the device from other sources than the IPP Printer object in the
608            same way that the Pause-Printer operation affects jobs that were
609            submitted to the IPP Printer object using IPP, depends on
610            implementation, i.e., on whether the IPP protocol is being used as a
611            universal management protocol or just to manage IPP jobs,
612            respectively.
613
614            The IPP Printer MUST accept the request in any state and transition
615            the Printer to the indicated new 'printer-state' before returning as
616            follows:
617
618            Current        New      'printer   IPP Printer's response status
619            'printer-    'printer-   -state-          code and action:
620            state'       state'    reasons'
621
622            'idle'       'stopped'    'paused'  'successful-ok'
623            'processing' 'processing' 'moving-  OPTION 1: 'successful-ok';
624                                                      to-       Later, when all output has
625                                                      paused'   stopped, the 'printer-state'
626                                                                            becomes 'stopped', and the
627                                                                            'paused' value replaces the
628                                                                            'moving-to-paused' value in the
629                                                                            'printer-state-reasons'
630                                                                            attribute
631            'processing' 'stopped'    'paused'  OPTION 2: 'successful-ok';
632                                                                            all device output stopped
633                                                                            immediately
634            'stopped'    'stopped'    'paused'  'successful-ok'
635
636            Access Rights: The authenticated user (see section 8.3) performing
637            this operation must be an operator or administrator of the Printer
638            object (see Sections 1 and 8.5).   Otherwise, the IPP Printer MUST
639            reject the operation and return:  'client-error-forbidden', 'client-
640            error-not-authenticated', or 'client-error-not-authorized' as
641            appropriate.
642
643            3.2.7.1 Pause-Printer Request
644
645            The following groups of attributes are part of the Pause-Printer
646            Request:
647
648            Group 1: Operation Attributes
649
650            Natural Language and Character Set:
651            The 'attributes-charset' and 'attributes-natural-language'
652            attributes as described in section 3.1.4.1.
653
654            Target:
655            The 'printer-uri' (uri) operation attribute which is the target
656            for this operation as described in section 3.1.5.
657
658            Requesting User Name:
659            The 'requesting-user-name' (name(MAX)) attribute SHOULD be
660            supplied by the client as described in section 8.3.
661
662            3.2.7.2 Pause-Printer Response
663
664            The following groups of attributes are part of the Pause-Printer
665            Response:
666
667            Group 1: Operation Attributes
668
669            Status Message:
670            In addition to the REQUIRED status code returned in every
671            response, the response OPTIONALLY includes a 'status-message'
672            (text(255)) and/or a 'detailed-status-message' (text(MAX))
673            operation attribute as described in sections 13 and  3.1.6.
674
675            Natural Language and Character Set:
676            The 'attributes-charset' and 'attributes-natural-language'
677            attributes as described in section 3.1.4.2.
678
679            Group 2: Unsupported Attributes
680
681            See section 3.1.7 for details on returning Unsupported Attributes.
682
683   
684    """
685        raise ipp.errors.ServerErrorOperationNotSupported
686
687    @handler_for(ipp.OperationCodes.RESUME_PRINTER)
688    def resume_printer(self, request, response):
689        """
690        3.2.8 Resume-Printer Operation
691
692        This operation allows a client to resume the Printer object
693        scheduling jobs on all its devices.  The Printer object MUST remove
694        the 'paused' and 'moving-to-paused' values from the Printer object's
695        'printer-state-reasons' attribute, if present.  If there are no other
696        reasons to keep a device paused (such as media-jam), the IPP Printer
697        is free to transition itself to the 'processing' or 'idle' states,
698        depending on whether there are jobs to be processed or not,
699        respectively, and the device(s) resume processing jobs.
700
701        If the Pause-Printer operation is supported, then the Resume-Printer
702        operation MUST be supported, and vice-versa.
703
704        The IPP Printer removes the 'printer-stopped' value from any job's
705        'job-state-reasons' attributes contained in that Printer.
706
707        The IPP Printer MUST accept the request in any state, transition the
708        Printer object to the indicated new state as follows:
709
710
711        Current    New 'printer-  IPP Printer's response status code and
712        'printer-      state'                     action:
713        state'
714
715        'idle'       'idle'         'successful-ok'
716        'processing' 'processing'   'successful-ok'
717
718        'stopped'    'processing'   'successful-ok';
719                                                   when there are jobs to be processed
720        'stopped'    'idle'         'successful-ok';
721                                                   when there are no jobs to be processed.
722
723        Access Rights: The authenticated user (see section 8.3) performing
724        this operation must be an operator or administrator of the Printer
725        object (see Sections 1 and 8.5).  Otherwise, the IPP Printer MUST
726        reject the operation and return:  'client-error-forbidden', 'client-
727        error-not-authenticated', or 'client-error-not-authorized' as
728        appropriate.
729
730        The Resume-Printer Request and Resume-Printer Response have the same
731        attribute groups and attributes as the Pause-Printer operation (see
732        sections 3.2.7.1 and 3.2.7.2).                 
733        """
734        raise ipp.errors.ServerErrorOperationNotSupported
735
736    @handler_for(ipp.OperationCodes.GET_PRINTER_ATTRIBUTES)
737    def get_printer_attributes(self, request, response):
738        """RFC 2911: 3.2.5 Get-Printer-Attributes Operation
739
740        This REQUIRED operation allows a client to request the values
741        of the attributes of a Printer object.
742       
743        In the request, the client supplies the set of Printer
744        attribute names and/or attribute group names in which the
745        requester is interested. In the response, the Printer object
746        returns a corresponding attribute set with the appropriate
747        attribute values filled in.
748
749        Request
750        -------
751
752        Group 1: Operation Attributes
753            REQUIRED 'attributes-charset'
754            REQUIRED 'attributes-natural-language'
755            REQUIRED 'printer-uri' (uri)
756            OPTIONAL 'requesting-user-name' (name(MAX))
757            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
758            OPTIONAL 'document-format' (mimeMediaType)
759           
760        Response
761        --------
762
763        Group 1: Operation Attributes
764            OPTIONAL 'status-message' (text(255))
765            OPTIONAL 'detailed-status-message' (text(MAX))
766            REQUIRED 'attributes-charset'
767            REQUIRED 'attributes-natural-language'
768        Group 2: Unsupported Attributes
769        Group 3: Printer Object Attributes
770
771        """
772
773        operation = request.attribute_groups[0]
774
775        printer_uri = None
776        requesting_user_name = None
777        requested_attributes = None
778        document_format = None
779
780        # requested printer uri
781        if 'printer-uri' not in operation:
782            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
783        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
784        if printer_uri not in self.printer.uris:
785            raise ipp.errors.ClientErrorAttributes(
786                str(operation['printer-uri']), operation['printer-uri'])
787
788        # optional attributes
789        if 'requesting-user-name' in operation:
790            user_name = verify_attribute(
791                operation['requesting-user-name'], ipp.RequestingUserName)[0]
792           
793        if 'requested-attributes' in operation:
794            requested_attributes = verify_attribute(
795                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
796
797        if 'document-format' in operation:
798            pass # XXX: todo
799
800        # get attributes from the printer and add to response
801        response.attribute_groups.append(ipp.AttributeGroup(
802            ipp.AttributeTags.PRINTER,
803            self.printer.get_printer_attributes(
804                requested_attributes=requested_attributes)))
805
806    @handler_for(ipp.OperationCodes.SET_PRINTER_ATTRIBUTES)
807    def set_printer_attributes(self, request, response):
808
809        raise ipp.errors.ServerErrorOperationNotSupported
810
811    ##### Job Commands
812
813    @handler_for(ipp.OperationCodes.CANCEL_JOB)
814    def cancel_job(self, request, response):
815        """3.3.3 Cancel-Job Operation
816
817        This REQUIRED operation allows a client to cancel a Print Job from
818        the time the job is created up to the time it is completed, canceled,
819        or aborted. Since a Job might already be printing by the time a
820        Cancel-Job is received, some media sheet pages might be printed
821        before the job is actually terminated.
822
823        The IPP object MUST accept or reject the request based on the job's
824        current state and transition the job to the indicated new state as
825        follows:
826
827        Current State       New State           Response
828        -----------------------------------------------------------------
829        pending             canceled            successful-ok
830        pending-held        canceled            successful-ok
831        processing          canceled            successful-ok
832        processing          processing          successful-ok               See Rule 1
833        processing          processing          client-error-not-possible   See Rule 2
834        processing-stopped  canceled            successful-ok
835        processing-stopped  processing-stopped  successful-ok               See Rule 1
836        processing-stopped  processing-stopped  client-error-not-possible   See Rule 2
837        completed           completed           client-error-not-possible
838        canceled            canceled            client-error-not-possible
839        aborted             aborted             client-error-not-possible
840
841        Rule 1: If the implementation requires some measurable time to
842        cancel the job in the 'processing' or 'processing-stopped' job
843        states, the IPP object MUST add the 'processing-to-stop-point'
844        value to the job's 'job-state-reasons' attribute and then
845        transition the job to the 'canceled' state when the processing
846        ceases (see section 4.3.8).
847
848        Rule 2: If the Job object already has the
849        'processing-to-stop-point' value in its 'job-state-reasons'
850        attribute, then the Printer object MUST reject a Cancel-Job
851        operation.
852
853        Access Rights: The authenticated user (see section 8.3)
854        performing this operation must either be the job owner or an
855        operator or administrator of the Printer object (see Sections
856        1 and 8.5).  Otherwise, the IPP object MUST reject the
857        operation and return: 'client-error-forbidden',
858        'client-error-not-authenticated', or
859        'client-error-not-authorized' as appropriate.
860
861        Request
862        -------
863
864        Group 1: Operation Attributes
865            REQUIRED 'attributes-charset'
866            REQUIRED 'attributes-natural-language'
867            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
868              -or-   'job-uri' (uri)
869            OPTIONAL 'requesting-user-name' (name(MAX))
870            OPTIONAL 'message' (text(127))
871           
872        Response
873        --------
874
875        Group 1: Operation Attributes
876            OPTIONAL 'status-message' (text(255))
877            OPTIONAL 'detailed-status-message' (text(MAX))
878            REQUIRED 'attributes-charset'
879            REQUIRED 'attributes-natural-language'
880        Group 2: Unsupported Attributes
881
882        """
883
884        operation = request.attribute_groups[0]
885
886        job_id = None
887        printer_uri = None
888        requesting_user_name = None
889        message = None
890
891        # required attributes
892        if 'job-id' in operation and 'printer-uri' in operation:
893            job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
894            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
895            if printer_uri not in self.printer.uris:
896                raise ipp.errors.ClientErrorAttributes(
897                    str(operation['printer-uri']), operation['printer-uri'])
898
899        elif 'job-uri' in operation:
900            job_uri = verify_attribute(operation['job-uri'], ipp.JobUri)[0]
901            job_id = int(job_uri.split("/")[-1])
902
903        if 'requesting-user-name' in operation:
904            requesting_user_name = verify_attribute(
905                operation['requesting-user-name'], ipp.RequestingUserName)[0]
906
907        try:
908            self.printer.cancel_job(job_id, requesting_user_name=requesting_user_name)
909        except InvalidJobException:
910            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
911
912    @handler_for(ipp.OperationCodes.SEND_DOCUMENT)
913    def send_document(self, request, response):
914        """3.3.1 Send-Document Operation
915       
916        This OPTIONAL operation allows a client to create a
917        multi-document Job object that is initially 'empty' (contains
918        no documents). In the Create-Job response, the Printer object
919        returns the Job object's URI (the 'job-uri' attribute) and the
920        Job object's 32-bit identifier (the 'job-id' attribute). For
921        each new document that the client desires to add, the client
922        uses a Send-Document operation. Each Send- Document Request
923        contains the entire stream of document data for one document.
924
925        If the Printer supports this operation but does not support
926        multiple documents per job, the Printer MUST reject subsequent
927        Send-Document operations supplied with data and return the
928        'server-error-multiple- document-jobs-not-supported'. However,
929        the Printer MUST accept the first document with a 'true' or
930        'false' value for the 'last-document' operation attribute (see
931        below), so that clients MAY always submit one document jobs
932        with a 'false' value for 'last-document' in the first
933        Send-Document and a 'true' for 'last-document' in the second
934        Send-Document (with no data).
935       
936        Since the Create-Job and the send operations (Send-Document or
937        Send- URI operations) that follow could occur over an
938        arbitrarily long period of time for a particular job, a client
939        MUST send another send operation within an IPP Printer defined
940        minimum time interval after the receipt of the previous
941        request for the job. If a Printer object supports the
942        Create-Job and Send-Document operations, the Printer object
943        MUST support the 'multiple-operation-time-out' attribute (see
944        section 4.4.31). This attribute indicates the minimum number
945        of seconds the Printer object will wait for the next send
946        operation before taking some recovery action.
947
948        An IPP object MUST recover from an errant client that does not
949        supply a send operation, sometime after the minimum time
950        interval specified by the Printer object's
951        'multiple-operation-time-out' attribute.
952
953        Access Rights: The authenticated user (see section 8.3)
954        performing this operation must either be the job owner (as
955        determined in the Create-Job operation) or an operator or
956        administrator of the Printer object (see Sections 1 and
957        8.5). Otherwise, the IPP object MUST reject the operation and
958        return: 'client-error-forbidden', 'client-
959        error-not-authenticated', or 'client-error-not-authorized' as
960        appropriate.
961
962        Request
963        -------
964
965        Group 1: Operation Attributes
966            REQUIRED 'attributes-charset'
967            REQUIRED 'attributes-natural-language'
968            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
969              -or-   'job-uri' (uri)
970            OPTIONAL 'requesting-user-name' (name(MAX))
971            OPTIONAL 'document-name' (name(MAX))
972            OPTIONAL 'compression' (type3 keyword)
973            OPTIONAL 'document-format' (mimeMediaType)
974            OPTIONAL 'document-natural-language' (naturalLanguage)
975            OPTIONAL 'last-document' (boolean)
976        Group 2: Document Content
977           
978        Response
979        --------
980
981        Group 1: Operation Attributes
982            OPTIONAL 'status-message' (text(255))
983            OPTIONAL 'detailed-status-message' (text(MAX))
984            REQUIRED 'attributes-charset'
985            REQUIRED 'attributes-natural-language'
986        Group 2: Unsupported Attributes
987        Group 3: Job Object Attributes
988            REQUIRED 'job-uri' (uri)
989            REQUIRED 'job-id' (integer(1:MAX))
990            REQUIRED 'job-state' (type1 enum)
991            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
992            OPTIONAL 'job-state-message' (text(MAX))
993            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
994
995        """
996       
997        operation = request.attribute_groups[0]
998
999        job_id = None
1000        printer_uri = None
1001        requesting_user_name = None
1002        document_name = None
1003        compression = None
1004        document_format = None
1005        document_natural_language = None
1006        last_document = None
1007
1008        # required attributes
1009        if 'job-id' not in operation:
1010            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
1011        job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
1012
1013        if 'last-document' not in operation:
1014            raise ipp.errors.ClientErrorBadRequest("Missing 'last-document' attribute")
1015        last_document = verify_attribute(operation['last-document'], ipp.LastDocument)[0]
1016        if not last_document:
1017            raise ipp.errors.ServerErrorMultipleJobsNotSupported
1018
1019        # optional attributes
1020        if 'printer-uri' in operation:
1021            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
1022            if printer_uri not in self.printer.uris:
1023                raise ipp.errors.ClientErrorAttributes(
1024                    str(operation['printer-uri']), operation['printer-uri'])
1025
1026        if 'requesting-user-name' in operation:
1027            user_name = verify_attribute(
1028                operation['requesting-user-name'], ipp.RequestingUserName)[0]
1029
1030        if 'document-name' in operation:
1031            document_name = verify_attribute(
1032                operation['document-name'], ipp.DocumentName)[0]
1033
1034        if 'compression' in operation:
1035            compression = verify_attribute(
1036                operation['compression'], ipp.Compression)[0]
1037
1038        if 'document-format' in operation:
1039            document_format = verify_attribute(
1040                operation['document-format'], ipp.DocumentFormat)[0]
1041
1042        if 'document-natural-language' in operation:
1043            document_natural_language = verify_attribute(
1044                operation['document_natural_language'],
1045                ipp.DocumentNaturalLanguage)[0]
1046
1047        try:
1048            self.printer.send_document(
1049                job_id,
1050                request.data,
1051                document_name=document_name,
1052                document_format=document_format,
1053                document_natural_language=document_natural_language,
1054                requesting_user_name=user_name,
1055                compression=compression,
1056                last_document=last_document)
1057            attrs = self.printer.get_job_attributes(job_id)
1058        except InvalidJobException:
1059            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
1060
1061        response.attribute_groups.append(ipp.AttributeGroup(
1062            ipp.AttributeTags.JOB, attrs))
1063
1064    @handler_for(ipp.OperationCodes.SEND_URI)
1065    def send_uri(self, request, response):
1066
1067        """3.2.2 Send URI
1068
1069        This OPTIONAL operation is identical to the Send-Document
1070        operation (see section 3.3.1) except that a client MUST supply
1071        a URI reference ('document-uri' operation attribute) rather
1072        than the document data itself.  If a Printer object supports
1073        this operation, clients can use both Send-URI or Send-Document
1074        operations to add new documents to an existing multi-document
1075        Job object.  However, if a client needs to indicate that the
1076        previous Send-URI or Send-Document was the last document, the
1077        client MUST use the Send-Document operation with no document
1078        data and the 'last-document' flag set to 'true' (rather than
1079        using a Send-URI operation with no 'document-uri' operation
1080        attribute).
1081
1082        If a Printer object supports this operation, it MUST also
1083        support the Print-URI operation (see section 3.2.2).
1084
1085        The Printer object MUST validate the syntax and URI scheme of
1086        the supplied URI before returning a response, just as in the
1087        Print-URI operation.  The IPP Printer MAY validate the
1088        accessibility of the document as part of the operation or
1089        subsequently (see section 3.2.2).
1090
1091        Request
1092        -------
1093
1094        Group 1: Operation Attributes
1095            REQUIRED 'attributes-charset'
1096            REQUIRED 'attributes-natural-language'
1097            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
1098            REQUIRED 'document-uri' (uri)
1099            OPTIONAL 'job-uri' (uri)
1100            OPTIONAL 'requesting-user-name' (name(MAX))
1101            OPTIONAL 'document-name' (name(MAX))
1102            OPTIONAL 'compression' (type3 keyword)
1103            OPTIONAL 'document-format' (mimeMediaType)
1104            OPTIONAL 'document-natural-language' (naturalLanguage)
1105           
1106        Response
1107        --------
1108
1109        Group 1: Operation Attributes
1110            OPTIONAL 'status-message' (text(255))
1111            OPTIONAL 'detailed-status-message' (text(MAX))
1112            REQUIRED 'attributes-charset'
1113            REQUIRED 'attributes-natural-language'
1114        Group 2: Unsupported Attributes
1115        Group 3: Job Object Attributes
1116            REQUIRED 'job-uri' (uri)
1117            REQUIRED 'job-id' (integer(1:MAX))
1118            REQUIRED 'job-state' (type1 enum)
1119            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
1120            OPTIONAL 'job-state-message' (text(MAX))
1121            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
1122
1123        """
1124       
1125        operation = request.attribute_groups[0]
1126
1127        job_id = None
1128        printer_uri = None
1129        requesting_user_name = None
1130        document_name = None
1131        compression = None
1132        document_format = None
1133        document_natural_language = None
1134        last_document = None
1135
1136        # required attributes
1137        if 'job-id' not in operation:
1138            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
1139        job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
1140
1141        if 'last-document' not in operation:
1142            raise ipp.errors.ClientErrorBadRequest("Missing 'last-document' attribute")
1143        last_document = verify_attribute(operation['last-document'], ipp.LastDocument)[0]
1144
1145        if 'document-uri' not in operation:
1146            raise ipp.errors.ClientErrorBadRequest("Missing 'document-uri' attribute")
1147        document_uri = verify_attribute(operation['document-uri'], ipp.DocumentUri)[0]
1148        if not last_document:
1149            raise ipp.errors.ServerErrorMultipleJobsNotSupported
1150
1151        # optional attributes
1152        if 'printer-uri' in operation:
1153            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
1154            if printer_uri not in self.printer.uris:
1155                raise ipp.errors.ClientErrorAttributes(
1156                    str(operation['printer-uri']), operation['printer-uri'])
1157
1158        if 'requesting-user-name' in operation:
1159            user_name = verify_attribute(
1160                operation['requesting-user-name'], ipp.RequestingUserName)[0]
1161
1162        if 'document-name' in operation:
1163            document_name = verify_attribute(
1164                operation['document-name'], ipp.DocumentName)[0]
1165
1166        if 'compression' in operation:
1167            compression = verify_attribute(
1168                operation['compression'], ipp.Compression)[0]
1169
1170        if 'document-format' in operation:
1171            document_format = verify_attribute(
1172                operation['document-format'], ipp.DocumentFormat)[0]
1173
1174        if 'document-natural-language' in operation:
1175            document_natural_language = verify_attribute(
1176                operation['document_natural_language'],
1177                ipp.DocumentNaturalLanguage)[0]
1178
1179        try:
1180            self.printer.send_uri(
1181                job_id,
1182                document_uri,
1183                document_name=document_name,
1184                document_format=document_format,
1185                document_natural_language=document_natural_language,
1186                requesting_user_name=user_name,
1187                compression=compression,
1188                last_document=last_document)
1189            attrs = self.printer.get_job_attributes(job_id)
1190        except InvalidJobException:
1191            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
1192
1193        response.attribute_groups.append(ipp.AttributeGroup(
1194            ipp.AttributeTags.JOB, attrs))
1195
1196    @handler_for(ipp.OperationCodes.GET_JOB_ATTRIBUTES)
1197    def get_job_attributes(self, request, response):
1198        """3.3.4 Get-Job-Attributes Operation
1199
1200        This REQUIRED operation allows a client to request the values
1201        of attributes of a Job object and it is almost identical to
1202        the Get- Printer-Attributes operation (see section 3.2.5). The
1203        only differences are that the operation is directed at a Job
1204        object rather than a Printer object, there is no
1205        'document-format' operation attribute used when querying a Job
1206        object, and the returned attribute group is a set of Job
1207        object attributes rather than a set of Printer object
1208        attributes.
1209
1210        For Jobs, the possible names of attribute groups are:
1211          - 'job-template': the subset of the Job Template attributes
1212            that apply to a Job object (the first column of the table
1213            in Section 4.2) that the implementation supports for Job
1214            objects.
1215          - 'job-description': the subset of the Job Description
1216            attributes specified in Section 4.3 that the
1217            implementation supports for Job objects.
1218          - 'all': the special group 'all' that includes all
1219            attributes that the implementation supports for Job
1220            objects.
1221
1222        Since a client MAY request specific attributes or named
1223        groups, there is a potential that there is some overlap. For
1224        example, if a client requests, 'job-name' and
1225        'job-description', the client is actually requesting the
1226        'job-name' attribute once by naming it explicitly, and once by
1227        inclusion in the 'job-description' group. In such cases, the
1228        Printer object NEED NOT return the attribute only once in the
1229        response even if it is requested multiple times. The client
1230        SHOULD NOT request the same attribute in multiple ways.
1231
1232        It is NOT REQUIRED that a Job object support all attributes
1233        belonging to a group (since some attributes are
1234        OPTIONAL). However it is REQUIRED that each Job object support
1235        all these group names.
1236
1237        Request
1238        -------
1239
1240        Group 1: Operation Attributes
1241            REQUIRED 'attributes-charset'
1242            REQUIRED 'attributes-natural-language'
1243            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
1244              -or-   'job-uri' (uri)
1245            OPTIONAL 'requesting-user-name' (name(MAX))
1246            OPTIONAL 'requested-attributes' (1setOf keyword)
1247           
1248        Response
1249        --------
1250
1251        Group 1: Operation Attributes
1252            OPTIONAL 'status-message' (text(255))
1253            OPTIONAL 'detailed-status-message' (text(MAX))
1254            REQUIRED 'attributes-charset'
1255            REQUIRED 'attributes-natural-language'
1256        Group 2: Unsupported Attributes
1257        Group 3: Job Object Attributes
1258
1259        """
1260       
1261        operation = request.attribute_groups[0]
1262
1263        job_id = None
1264        printer_uri = None
1265        requesting_user_name = None
1266        requested_attributes = None
1267
1268        # required attributes
1269        if 'job-id' not in operation:
1270            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
1271        job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
1272
1273        # optional attributes
1274        if 'printer-uri' in operation:
1275            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
1276            if printer_uri not in self.printer.uris:
1277                raise ipp.errors.ClientErrorAttributes(
1278                    str(operation['printer-uri']), operation['printer-uri'])
1279
1280        # optional attributes
1281        if 'requesting-user-name' in operation:
1282            user_name = verify_attribute(
1283                operation['requesting-user-name'], ipp.RequestingUserName)[0]
1284           
1285        if 'requested-attributes' in operation:
1286            requested_attributes = verify_attribute(
1287                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
1288
1289        # get the job attributes and add them to the response
1290        try:
1291            attrs = self.printer.get_job_attributes(
1292                job_id,
1293                requested_attributes=requested_attributes)
1294        except InvalidJobException:
1295            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
1296
1297        response.attribute_groups.append(ipp.AttributeGroup(
1298            ipp.AttributeTags.JOB, attrs))
1299
1300    @handler_for(ipp.OperationCodes.SET_JOB_ATTRIBUTES)
1301    def set_job_attributes(self, request, response):
1302       
1303        raise ipp.errors.ServerErrorOperationNotSupported
1304
1305    @handler_for(ipp.OperationCodes.RESTART_JOB)
1306    def restart_job(self, request, response):
1307        raise ipp.errors.ServerErrorOperationNotSupported
1308
1309    @handler_for(ipp.OperationCodes.PROMOTE_JOB)
1310    def promote_job(self, request, response):
1311        raise ipp.errors.ServerErrorOperationNotSupported
1312
1313    ##### CUPS Specific Commands
1314
1315    @handler_for(ipp.OperationCodes.CUPS_GET_DOCUMENT)
1316    def cups_get_document(self, request, response):
1317        raise ipp.errors.ServerErrorOperationNotSupported
1318
1319    @handler_for(ipp.OperationCodes.CUPS_GET_DEFAULT)
1320    def cups_get_default(self, request, response):
1321        """The CUPS-Get-Default operation (0x4001) returns the default
1322        printer URI and attributes.
1323
1324        Request
1325        -------
1326
1327        Group 1: Operation Attributes
1328            REQUIRED 'attributes-charset'
1329            REQUIRED 'attributes-natural-language'
1330            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
1331
1332        Response
1333        --------
1334
1335        Group 1: Operation Attributes
1336            OPTIONAL 'status-message' (text(255))
1337            REQUIRED 'attributes-charset'
1338            REQUIRED 'attributes-natural-language'
1339        Group 2: Printer Object Attributes
1340
1341        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_DEFAULT )
1342
1343        """
1344
1345        operation = request.attribute_groups[0]
1346        requested_attributes = None
1347       
1348        if 'requested-attributes' in operation:
1349            requested_attributes = verify_attribute(
1350                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
1351
1352        # get attributes from the printer and add to response
1353        attrs = self.printer.get_printer_attributes(
1354            requested_attributes=requested_attributes)
1355        response.attribute_groups.append(ipp.AttributeGroup(
1356            ipp.AttributeTags.PRINTER, attrs))
1357
1358    @handler_for(ipp.OperationCodes.CUPS_GET_PRINTERS)
1359    def cups_get_printers(self, request, response):
1360        """The CUPS-Get-Printers operation (0x4002) returns the
1361        printer attributes for every printer known to the system. This
1362        may include printers that are not served directly by the
1363        server.
1364
1365        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_PRINTERS )
1366           
1367        """
1368
1369        # get attributes from the printer and add to response
1370        response.attribute_groups.append(ipp.AttributeGroup(
1371            ipp.AttributeTags.PRINTER, self.printer.get_printer_attributes()))
1372
1373    @handler_for(ipp.OperationCodes.CUPS_GET_CLASSES)
1374    def cups_get_classes(self, request, response):
1375        """The CUPS-Get-Classes operation (0x4005) returns the printer
1376        attributes for every printer class known to the system. This
1377        may include printer classes that are not served directly by
1378        the server.
1379
1380        Request
1381        -------
1382
1383        Group 1: Operation Attributes
1384            REQUIRED 'attributes-charset'
1385            REQUIRED 'attributes-natural-language'
1386            OPTIONAL 'first-printer-name' (name(127)) CUPS 1.2/Mac OS X 10.5
1387            OPTIONAL 'limit' (integer (1:MAX))
1388            OPTIONAL 'printer-location' (text(127)) CUPS 1.1.7
1389            OPTIONAL 'printer-type' (type2 enum) CUPS 1.1.7
1390            OPTIONAL 'printer-type-mask' (type2 enum) CUPS 1.1.7
1391            OPTIONAL 'requested-attributes' (1setOf keyword)
1392            OPTOINAL 'requested-user-name' (name(127)) CUPS 1.2/Mac OS X 10.5
1393            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
1394
1395        Response
1396        --------
1397
1398        Group 1: Operation Attributes
1399            OPTIONAL 'status-message' (text(255))
1400            REQUIRED 'attributes-charset'
1401            REQUIRED 'attributes-natural-language'
1402        Group 2: Printer Class Object Attributes
1403
1404        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_CLASSES )
1405
1406        """
1407
1408        raise ipp.errors.ServerErrorOperationNotSupported
1409
Note: See TracBrowser for help on using the repository browser.