source: server/lib/gutenbach/server/requests.py @ 94a4825

no-cups
Last change on this file since 94a4825 was 94a4825, checked in by Jessica B. Hamrick <jhamrick@…>, 12 years ago

Add handler decorators for other handlers and throw an exception if they're not implemented; add documentation for create-job handler

  • Property mode set to 100644
File size: 14.0 KB
Line 
1from gutenbach.server.printer import GutenbachPrinter
2import gutenbach.ipp as ipp
3import gutenbach.ipp.constants as consts
4import logging
5import traceback
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
22class GutenbachRequestHandler(object):
23
24    def __init__(self):
25        self.printers = {
26            "test": GutenbachPrinter(name="test")
27            }
28        self.default = "test"
29   
30    def handle(self, request):
31        # look up the handler
32        handler = None
33        handler_name = None
34        for d in dir(self):
35            if getattr(getattr(self, d), "ipp_operation", None) == request.operation_id:
36                handler_name = d
37                break
38        # we couldn't find a handler, so default to unknown operation
39        if handler_name is None:
40            handler_name = "unknown_operation"
41        # call the handler
42        handler = getattr(self, handler_name)
43        logger.info("Handling request of type '%s'" % handler_name)
44
45        # Try to handle the request
46        try:
47            response = handler(request)
48
49        # Handle any errors that occur.  If an exception occurs that
50        # is an IPP error, then we can get the error code from the
51        # exception itself.
52        except ipp.errors.IPPException:
53            exctype, excval, exctb = sys.exc_info()
54            logger.error(traceback.format_exc())
55            response = ipp.ops.make_empty_response(request)
56            excval.update_response(response)
57
58        # If it wasn't an IPP error, then it's our fault, so mark it
59        # as an internal server error
60        except Exception:
61            logger.error(traceback.format_exc())
62            response = ipp.ops.make_empty_response(request)
63            response.operation_id = ipp.StatusCodes.INTERNAL_ERROR
64
65        return response
66
67    def unknown_operation(self, request):
68        logger.warning("Received unknown operation 0x%x" % request.operation_id)
69        response = ipp.ops.make_empty_response(request)
70        response.operation_id = consts.StatusCodes.OPERATION_NOT_SUPPORTED
71        return response
72       
73    ##### Printer Commands
74
75    @handler_for(consts.Operations.PRINT_JOB)
76    def print_job(self, request):
77        """RFC 2911: 3.2.1 Print-Job Operation
78
79        This REQUIRED operation allows a client to submit a print job
80        with only one document and supply the document data (rather
81        than just a reference to the data). See Section 15 for the
82        suggested steps for processing create operations and their
83        Operation and Job Template attributes.
84
85        """
86       
87        raise NotImplementedError
88
89    @handler_for(consts.Operations.VALIDATE_JOB)
90    def validate_job(self, request):
91
92        raise NotImplementedError
93
94    @handler_for(consts.Operations.GET_JOBS)
95    def get_jobs(self, request):
96        """RFC 2911: 3.2.6 Get-Jobs Operation
97       
98        This REQUIRED operation allows a client to retrieve the list
99        of Job objects belonging to the target Printer object. The
100        client may also supply a list of Job attribute names and/or
101        attribute group names. A group of Job object attributes will
102        be returned for each Job object that is returned.
103
104        This operation is similar to the Get-Job-Attributes operation,
105        except that this Get-Jobs operation returns attributes from
106        possibly more than one object.
107
108        """
109
110        # verify the request and get an attribute dictionary
111        req_dict = ipp.ops.verify_get_jobs_request(request)
112
113        # lookup printer name
114        printer_name = req_dict['printer-uri']
115        if printer_name not in self.printers:
116            raise ipp.errors.Attributes(
117                "Invalid printer uri: %s" % printer_name,
118                [request.attribute_groups[0].attributes[2]])
119
120        # get the job attributes
121        jobs = [job.get_job_attributes(request) for job in \
122                self.printers[printer_name].get_jobs()]
123
124        # build the response
125        response = ipp.ops.make_get_jobs_response(jobs, request)
126        return response
127
128    @handler_for(consts.Operations.PRINT_URI)
129    def print_uri(self, request):
130        raise NotImplementedError
131
132    @handler_for(consts.Operations.CREATE_JOB)
133    def create_job(self, request):
134        """RFC 2911: 3.2.4 Create-Job Operation
135
136        This OPTIONAL operation is similar to the Print-Job operation
137        (section 3.2.1) except that in the Create-Job request, a
138        client does not supply document data or any reference to
139        document data. Also, the client does not supply any of the
140        'document-name', 'document- format', 'compression', or
141        'document-natural-language' operation attributes. This
142        operation is followed by one or more Send-Document or Send-URI
143        operations. In each of those operation requests, the client
144        OPTIONALLY supplies the 'document-name', 'document-format',
145        and 'document-natural-language' attributes for each document
146        in the multi-document Job object.
147
148        If a Printer object supports the Create-Job operation, it MUST
149        also support the Send-Document operation and also MAY support
150        the Send-URI operation.
151       
152        If the Printer object supports this operation, it MUST support
153        the 'multiple-operation-time-out' Printer attribute (see
154        section 4.4.31).  If the Printer object supports this
155        operation, then it MUST support the
156        'multiple-document-jobs-supported' Printer Description
157        attribute (see section 4.4.16) and indicate whether or not it
158        supports multiple-document jobs.
159       
160        If the Printer object supports this operation and supports
161        multiple documents in a job, then it MUST support the
162        'multiple-document- handling' Job Template job attribute with
163        at least one value (see section 4.2.4) and the associated
164        'multiple-document-handling- default' and
165        'multiple-document-handling-supported' Job Template Printer
166        attributes (see section 4.2).
167       
168        After the Create-Job operation has completed, the value of the
169        'job- state' attribute is similar to the 'job-state' after a
170        Print-Job, even though no document-data has arrived. A Printer
171        MAY set the 'job-data-insufficient' value of the job's
172        'job-state-reason' attribute to indicate that processing
173        cannot begin until sufficient data has arrived and set the
174        'job-state' to either 'pending' or 'pending-held'. A
175        non-spooling printer that doesn't implement the 'pending' job
176        state may even set the 'job-state' to 'processing', even
177        though there is not yet any data to process. See sections
178        4.3.7 and 4.3.8.
179       
180        """
181
182        raise NotImplementedError
183   
184    @handler_for(consts.Operations.PAUSE_PRINTER)
185    def pause_printer(self, request):
186        raise NotImplementedError
187
188    @handler_for(consts.Operations.RESUME_PRINTER)
189    def resume_printer(self, request):
190        raise NotImplementedError
191
192    @handler_for(consts.Operations.GET_PRINTER_ATTRIBUTES)
193    def get_printer_attributes(self, request):
194        """RFC 2911: 3.2.5 Get-Printer-Attributes Operation
195
196        This REQUIRED operation allows a client to request the values
197        of the attributes of a Printer object.
198       
199        In the request, the client supplies the set of Printer
200        attribute names and/or attribute group names in which the
201        requester is interested. In the response, the Printer object
202        returns a corresponding attribute set with the appropriate
203        attribute values filled in.
204
205        For Printer objects, the possible names of attribute groups are:
206       
207        - 'job-template': the subset of the Job Template attributes
208          that apply to a Printer object (the last two columns of the
209          table in Section 4.2) that the implementation supports for
210          Printer objects.
211
212        - 'printer-description': the subset of the attributes
213          specified in Section 4.4 that the implementation supports
214          for Printer objects.
215
216        - 'all': the special group 'all' that includes all attributes
217          that the implementation supports for Printer objects.
218       
219        Since a client MAY request specific attributes or named
220        groups, there is a potential that there is some overlap. For
221        example, if a client requests, 'printer-name' and 'all', the
222        client is actually requesting the 'printer-name' attribute
223        twice: once by naming it explicitly, and once by inclusion in
224        the 'all' group. In such cases, the Printer object NEED NOT
225        return each attribute only once in the response even if it is
226        requested multiple times. The client SHOULD NOT request the
227        same attribute in multiple ways.
228
229        It is NOT REQUIRED that a Printer object support all
230        attributes belonging to a group (since some attributes are
231        OPTIONAL). However, it is REQUIRED that each Printer object
232        support all group names.
233
234        """
235
236        # verify the request and get the attributes dictionary
237        req_dict = ipp.ops.verify_get_printer_attributes_request(request)
238
239        # lookup the printer name
240        printer_name = req_dict['printer-uri']
241        if printer_name not in self.printers:
242            raise ipp.errors.Attributes(
243                "Invalid printer uri: %s" % printer_name,
244                [request.attribute_groups[0].attributes[2]])
245
246        # bulid response
247        response = ipp.ops.make_get_printer_attributes_response(
248            self.printers[printer_name].get_printer_attributes(request), request)
249        return response
250
251    @handler_for(consts.Operations.SET_PRINTER_ATTRIBUTES)
252    def set_printer_attributes(self, request):
253        raise NotImplementedError
254
255    ##### Job Commands
256
257    @handler_for(consts.Operations.CANCEL_JOB)
258    def cancel_job(self, request):
259        raise NotImplementedError
260
261    @handler_for(consts.Operations.SEND_DOCUMENT)
262    def send_document(self, request):
263        raise NotImplementedError
264
265    @handler_for(consts.Operations.SEND_URI)
266    def send_uri(self, request):
267        raise NotImplementedError
268
269    @handler_for(consts.Operations.GET_JOB_ATTRIBUTES)
270    def get_job_attributes(self, request):
271       
272        # verify the request and get the attributes dictionary
273        req_dict = ipp.ops.verify_get_jobs_request(request)
274       
275        # lookup the printer name
276        printer_name = req_dict['printer-uri']
277        if printer_name not in self.printers:
278            raise ipp.errors.Attributes(
279                "Invalid printer uri: %s" % printer_name,
280                [request.attribute_groups[0].attributes[2]])
281
282        # lookup the job id
283        job_id = req_dict['job-id']
284        try: job = self.printers[printer_name].get_job(job_id)
285        except InvalidJobException:
286            raise ipp.errors.Attributes(
287                "Invalid job id: %d" % job_id,
288                [request.attribute_groups[0].attributes[2]]) # XXX: this is wrong
289
290        # XXX: we need to honor the things that the request actually asks for
291        # build the response
292        response = ipp.ops.make_get_job_attributes_response(
293            job.get_job_attributes(request), request)
294        return response
295
296    @handler_for(consts.Operations.SET_JOB_ATTRIBUTES)
297    def set_job_attributes(self, request):
298        raise NotImplementedError
299
300    @handler_for(consts.Operations.RESTART_JOB)
301    def restart_job(self, request):
302        raise NotImplementedError
303
304    @handler_for(consts.Operations.PROMOTE_JOB)
305    def promote_job(self, request):
306        raise NotImplementedError
307
308    ##### CUPS Specific Commands
309
310    @handler_for(consts.Operations.CUPS_GET_DOCUMENT)
311    def cups_get_document(self, request):
312        raise NotImplementedError
313
314    @handler_for(consts.Operations.CUPS_GET_DEFAULT)
315    def cups_get_default(self, request):
316        """The CUPS-Get-Default operation (0x4001) returns the default
317        printer URI and attributes.
318
319        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_DEFAULT )
320
321        """
322
323        # verify the request and get the attributes dictionary
324        req_dict = ipp.ops.verify_cups_get_default_request(request)
325        # build the response
326        response = ipp.ops.make_get_printer_attributes_response(
327            self.printers[self.default].get_printer_attributes(request), request)
328        return response
329
330    @handler_for(consts.Operations.CUPS_GET_PRINTERS)
331    def cups_get_printers(self, request):
332        """The CUPS-Get-Printers operation (0x4002) returns the
333        printer attributes for every printer known to the system. This
334        may include printers that are not served directly by the
335        server.
336
337        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_PRINTERS )
338           
339        """
340
341        # verify the request and get the attributes dictionary
342        req_dict = ipp.ops.verify_cups_get_printers_request(request)
343        # get the printer attributes
344        attrs = [self.printers[printer].get_printer_attributes(request) \
345                 for printer in self.printers]
346        # build the response
347        response = ipp.ops.make_cups_get_printers_response(attrs, request)
348        return response
349
350    @handler_for(consts.Operations.CUPS_GET_CLASSES)
351    def cups_get_classes(self, request):
352        """The CUPS-Get-Classes operation (0x4005) returns the printer
353        attributes for every printer class known to the system. This
354        may include printer classes that are not served directly by
355        the server.
356       
357        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_CLASSES )
358
359        """
360
361        # verify the request and get the attributes dictionaryu
362        req_dict = ipp.ops.verify_cups_get_classes_request(request)
363        # build the response
364        response = ipp.ops.make_cups_get_classes_response(request)
365        return response
Note: See TracBrowser for help on using the repository browser.