source: server/lib/gutenbach/server/printer.py @ b3a56af

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

Some IPP documentation in printer.py

  • Property mode set to 100644
File size: 40.3 KB
Line 
1from .errors import InvalidJobException, InvalidPrinterStateException, InvalidJobStateException
2from .job import GutenbachJob
3from gutenbach.ipp import PrinterStates as States
4import gutenbach.ipp as ipp
5import logging
6import time
7import threading
8import traceback
9import sys
10import tempfile
11from . import sync
12
13
14# initialize logger
15logger = logging.getLogger(__name__)
16
17class GutenbachPrinter(threading.Thread):
18
19    # for IPP
20    printer_attributes = [
21        "printer-uri-supported",
22        "uri-authentication-supported",
23        "uri-security-supported",
24        "printer-name",
25        "printer-state",
26        "printer-state-reasons",
27        "ipp-versions-supported",
28        "operations-supported",
29        "charset-configured",
30        "charset-supported",
31        "natural-language-configured",
32        "generated-natural-language-supported",
33        "document-format-default",
34        "document-format-supported",
35        "printer-is-accepting-jobs",
36        "queued-job-count",
37        "pdl-override-supported",
38        "printer-up-time",
39        "compression-supported",
40        "multiple-operation-time-out",
41        "multiple-document-jobs-supported",
42    ]
43
44    job_attributes = [
45        "job-id",
46        "job-name",
47        "job-originating-user-name",
48        "job-k-octets",
49        "job-state",
50        "job-printer-uri"
51    ]
52
53    operations = [
54        "print-job",
55        "validate-job",
56        "get-jobs",
57        "print-uri",
58        "create-job",
59        "pause-printer",
60        "resume-printer",
61        "get-printer-attributes",
62        "set-printer-attributes",
63        "cancel-job",
64        "send-document",
65        "send-uri",
66        "get-job-attributes",
67        "set-job-attributes",
68        "restart-job",
69        "promote-job"
70    ]
71       
72    def __init__(self, name, config, *args, **kwargs):
73
74        super(GutenbachPrinter, self).__init__(*args, **kwargs)
75       
76        self.name = name
77        self.config = config
78        self.time_created = int(time.time())
79
80        self.finished_jobs = []
81        self.pending_jobs = []
82        self.current_job = None
83        self.jobs = {}
84
85        self.lock = threading.RLock()
86        self._running = False
87        self.paused = False
88
89        # CUPS ignores jobs with id 0, so we have to start at 1
90        self._next_job_id = 1
91
92    @sync
93    def __repr__(self):
94        return str(self)
95
96    @sync
97    def __str__(self):
98        return "<Printer '%s'>" % self.name
99
100    def run(self):
101        self._running = True
102        while self._running:
103            with self.lock:
104                try:
105                    if self.current_job is None:
106                        self.start_job()
107                    elif self.current_job.is_done:
108                        self.complete_job()
109                except:
110                    self._running = False
111                    logger.fatal(traceback.format_exc())
112                    break
113            time.sleep(0.1)
114
115    def stop(self):
116        with self.lock:
117            for job in self.jobs.keys():
118                try:
119                    self.jobs[job].abort()
120                    del self.jobs[job]
121                except InvalidJobStateException:
122                    pass
123               
124            self._running = False
125        if self.ident is not None and self.isAlive():
126            self.join()
127
128    ######################################################################
129    ###                          Properties                            ###
130    ######################################################################
131
132    @property
133    def name(self):
134        return self._name
135    @name.setter
136    def name(self, val):
137        try:
138            self._name = str(val)
139        except:
140            self._name = "gutenbach-printer"
141
142    @property
143    def config(self):
144        return self._config
145    @config.setter
146    def config(self, val):
147        try:
148            _config = dict(val).copy()
149        except:
150            raise ValueError, "not a dictionary"
151        if 'ipp-versions' not in _config:
152            raise ValueError, "missing ipp-versions"
153        self._config = _config
154
155    @property
156    def uris(self):
157        uris = ["ipp://localhost:8000/printers/" + self.name,
158                "ipp://localhost/printers/" + self.name]
159        return uris
160   
161    @property
162    def uri(self):
163        return self.uris[0]
164
165    @property
166    @sync
167    def state(self):
168        if self.is_running and not self.paused:
169            if len(self.active_jobs) > 0:
170                state = States.PROCESSING
171            else:
172                state = States.IDLE
173        else:
174            state = States.STOPPED
175
176        return state
177
178    @property
179    @sync
180    def active_jobs(self):
181        jobs = self.pending_jobs[:]
182        if self.current_job is not None:
183            jobs.insert(0, self.current_job.id)
184        return jobs
185
186    @property
187    def is_running(self):
188        running = self.ident is not None and self.isAlive() and self._running
189        return running
190
191    ######################################################################
192    ###                            Methods                             ###
193    ######################################################################
194
195    @sync
196    def assert_running(self):
197        if not self.is_running:
198            raise RuntimeError, "%s not started" % str(self)
199
200    @sync
201    def start_job(self):
202        self.assert_running()
203        if not self.paused and self.current_job is None:
204            try:
205                job_id = self.pending_jobs.pop(0)
206                self.current_job = self.get_job(job_id)
207                self.current_job.play()
208            except IndexError:
209                self.current_job = None
210                   
211    @sync
212    def complete_job(self):
213        self.assert_running()
214        if not self.paused and self.current_job is not None:
215            try:
216                if not self.current_job.is_done:
217                    self.current_job.stop()
218            finally:
219                self.finished_jobs.append(self.current_job.id)
220                self.current_job = None
221
222    @sync
223    def get_job(self, job_id):
224        self.assert_running()
225        if job_id not in self.jobs:
226            raise InvalidJobException(job_id)
227        return self.jobs[job_id]
228
229    ######################################################################
230    ###                        IPP Attributes                          ###
231    ######################################################################
232
233    @property
234    def printer_uri_supported(self):
235        self.assert_running()
236        return ipp.PrinterUriSupported(self.uri)
237    @printer_uri_supported.setter
238    def printer_uri_supported(self, val):
239        self.assert_running()
240        raise ipp.errors.AttributesNotSettable("printer-uri-supported")
241
242    @property
243    def uri_authentication_supported(self):
244        self.assert_running()
245        return ipp.UriAuthenticationSupported("none")
246    @uri_authentication_supported.setter
247    def uri_authentication_supported(self, val):
248        self.assert_running()
249        raise ipp.errors.AttributesNotSettable("uri-authentication-supported")
250
251    @property
252    def uri_security_supported(self):
253        self.assert_running()
254        return ipp.UriSecuritySupported("none")
255    @uri_security_supported.setter
256    def uri_security_supported(self, val):
257        self.assert_running()
258        raise ipp.errors.AttributesNotSettable("uri-security-supported")
259
260    @property
261    def printer_name(self):
262        self.assert_running()
263        return ipp.PrinterName(self.name)
264    @printer_name.setter
265    def printer_name(self, val):
266        self.assert_running()
267        raise ipp.errors.AttributesNotSettable("printer-name")
268
269    @property
270    def printer_state(self):
271        self.assert_running()
272        return ipp.PrinterState(self.state)
273    @printer_state.setter
274    def printer_state(self, val):
275        self.assert_running()
276        raise ipp.errors.AttributesNotSettable("printer-state")
277
278    @property
279    def printer_state_reasons(self):
280        self.assert_running()
281        return ipp.PrinterStateReasons("none")
282    @printer_state_reasons.setter
283    def printer_state_reasons(self, val):
284        self.assert_running()
285        raise ipp.errors.AttributesNotSettable("printer-state-reasons")
286
287    @property
288    def ipp_versions_supported(self):
289        self.assert_running()
290        return ipp.IppVersionsSupported(*self.config['ipp-versions'])
291    @ipp_versions_supported.setter
292    def ipp_versions_supported(self, val):
293        self.assert_running()
294        raise ipp.errors.AttributesNotSettable("ipp-versions-supported")
295
296    # XXX: We should query ourself for the supported operations
297    @property
298    def operations_supported(self):
299        self.assert_running()
300        return ipp.OperationsSupported(ipp.OperationCodes.GET_JOBS)
301    @operations_supported.setter
302    def operations_supported(self, val):
303        self.assert_running()
304        raise ipp.errors.AttributesNotSettable("operations-supported")
305
306    @property
307    def charset_configured(self):
308        self.assert_running()
309        return ipp.CharsetConfigured("utf-8") # XXX
310    @charset_configured.setter
311    def charset_configured(self, val):
312        self.assert_running()
313        raise ipp.errors.AttributesNotSettable("charset-configured")
314       
315    @property
316    def charset_supported(self):
317        self.assert_running()
318        return ipp.CharsetSupported("utf-8") # XXX
319    @charset_supported.setter
320    def charset_supported(self, val):
321        self.assert_running()
322        raise ipp.errors.AttributesNotSettable("charset-supported")
323
324    @property
325    def natural_language_configured(self):
326        self.assert_running()
327        return ipp.NaturalLanguageConfigured("en-us")
328    @natural_language_configured.setter
329    def natural_language_configured(self, val):
330        self.assert_running()
331        raise ipp.errors.AttributesNotSettable("natural-language-configured")
332
333    @property
334    def generated_natural_language_supported(self):
335        self.assert_running()
336        return ipp.GeneratedNaturalLanguageSupported("en-us")
337    @generated_natural_language_supported.setter
338    def generated_natural_language_supported(self, val):
339        self.assert_running()
340        raise ipp.errors.AttributesNotSettable("generated-natural-language-supported")
341
342    @property
343    def document_format_default(self):
344        self.assert_running()
345        return ipp.DocumentFormatDefault("application/octet-stream")
346    @document_format_default.setter
347    def document_format_default(self, val):
348        self.assert_running()
349        raise ipp.errors.AttributesNotSettable("document-format-default")
350
351    @property
352    def document_format_supported(self):
353        self.assert_running()
354        return ipp.DocumentFormatSupported("application/octet-stream", "audio/mp3")
355    @document_format_supported.setter
356    def document_format_supported(self, val):
357        self.assert_running()
358        raise ipp.errors.AttributesNotSettable("document-format-supported")
359
360    @property
361    def printer_is_accepting_jobs(self):
362        self.assert_running()
363        return ipp.PrinterIsAcceptingJobs(True)
364    @printer_is_accepting_jobs.setter
365    def printer_is_accepting_jobs(self, val):
366        self.assert_running()
367        raise ipp.errors.AttributesNotSettable("printer-is-accepting-jobs")
368
369    @property
370    def queued_job_count(self):
371        self.assert_running()
372        return ipp.QueuedJobCount(len(self.active_jobs))
373    @queued_job_count.setter
374    def queued_job_count(self, val):
375        self.assert_running()
376        raise ipp.errors.AttributesNotSettable("queued-job-count")
377
378    @property
379    def pdl_override_supported(self):
380        self.assert_running()
381        return ipp.PdlOverrideSupported("not-attempted")
382    @pdl_override_supported.setter
383    def pdl_override_supported(self, val):
384        self.assert_running()
385        raise ipp.errors.AttributesNotSettable("pdl-override-supported")
386
387    @property
388    def printer_up_time(self):
389        self.assert_running()
390        return ipp.PrinterUpTime(int(time.time()) - self.time_created)
391    @printer_up_time.setter
392    def printer_up_time(self, val):
393        self.assert_running()
394        raise ipp.errors.AttributesNotSettable("printer-up-time")
395
396    @property
397    def compression_supported(self):
398        self.assert_running()
399        return ipp.CompressionSupported("none")
400    @compression_supported.setter
401    def compression_supported(self, val):
402        self.assert_running()
403        raise ipp.errors.AttributesNotSettable("compression-supported")
404
405    @property
406    def multiple_operation_time_out(self):
407        self.assert_running()
408        return ipp.MultipleOperationTimeOut(240)
409    @multiple_operation_time_out.setter
410    def multiple_operation_time_out(self, val):
411        self.assert_running()
412        raise ipp.errors.AttributesNotSettable("multiple-operation-time-out")
413
414    @property
415    def multiple_document_jobs_supported(self):
416        self.assert_running()
417        return ipp.MultipleDocumentJobsSupported(False)
418    @multiple_document_jobs_supported.setter
419    def multiple_document_jobs_supported(self, val):
420        self.assert_running()
421        raise ipp.errors.AttributesNotSettable("multiple-document-jobs-supported")
422
423    ######################################################################
424    ###                      Job IPP Attributes                        ###
425    ######################################################################
426
427    def job_id(self, job_id):
428        self.assert_running()
429        job = self.get_job(job_id)
430        return ipp.JobId(job.id)
431
432    def job_name(self, job_id):
433        self.assert_running()
434        job = self.get_job(job_id)
435        return ipp.JobName(job.name)
436
437    def job_originating_user_name(self, job_id):
438        self.assert_running()
439        job = self.get_job(job_id)
440        return ipp.JobOriginatingUserName(job.creator)
441
442    def job_k_octets(self, job_id):
443        self.assert_running()
444        job = self.get_job(job_id)
445        return ipp.JobKOctets(job.size)
446
447    def job_state(self, job_id):
448        self.assert_running()
449        job = self.get_job(job_id)
450        return ipp.JobState(job.state)
451
452    def job_printer_uri(self, job_id):
453        self.assert_running()
454        job = self.get_job(job_id)
455        return ipp.JobPrinterUri(self.uri)
456
457    ######################################################################
458    ###                        IPP Operations                          ###
459    ######################################################################
460
461    @sync
462    def print_job(self, document, document_name=None, document_format=None,
463                  document_natural_language=None, requesting_user_name=None,
464                  compression=None, job_name=None, job_k_octets=None):
465        """RFC 2911: 3.2.1 Print-Job Operation
466       
467        This REQUIRED operation allows a client to submit a print job
468        with only one document and supply the document data (rather
469        than just a reference to the data). See Section 15 for the
470        suggested steps for processing create operations and their
471        Operation and Job Template attributes.
472
473        Parameters
474        ----------
475        document (file)
476            an open file handler to the document
477        document_name (string)
478            the name of the document
479        document_format (string)
480            the encoding/format of the document
481        document_natural_language (string)
482            if the document is a text file, what language it is in
483        requesting_user_name (string)
484            the user name of the job owner
485        compression (string)
486            the form of compression used on the file
487        job_name (string)
488            the name that the job should be called
489        job_k_octets (int)
490            the size of the job in bytes
491
492        """
493       
494        self.assert_running()
495
496        # create the job
497        job_id = self.create_job(
498            requesting_user_name=requesting_user_name,
499            job_name=job_name,
500            job_k_octets=job_k_octets)
501       
502        # send the document
503        self.send_document(
504            job_id,
505            document,
506            document_name=document_name,
507            document_format=document_format,
508            document_natural_language=document_natural_language,
509            requesting_user_name=requesting_user_name,
510            compression=compression,
511            last_document=False)
512
513        return job_id
514
515    @sync
516    def validate_job(self, document_name=None, document_format=None,
517                     document_natural_language=None, requesting_user_name=None,
518                     compression=None, job_name=None, job_k_octets=None):
519        """RFC 2911: 3.2.3 Validate-Job Operation
520
521        This REQUIRED operation is similar to the Print-Job operation
522        (section 3.2.1) except that a client supplies no document data
523        and the Printer allocates no resources (i.e., it does not
524        create a new Job object).  This operation is used only to
525        verify capabilities of a printer object against whatever
526        attributes are supplied by the client in the Validate-Job
527        request.  By using the Validate-Job operation a client can
528        validate that an identical Print-Job operation (with the
529        document data) would be accepted. The Validate-Job operation
530        also performs the same security negotiation as the Print-Job
531        operation (see section 8), so that a client can check that the
532        client and Printer object security requirements can be met
533        before performing a Print-Job operation.
534
535        The Validate-Job operation does not accept a 'document-uri'
536        attribute in order to allow a client to check that the same
537        Print-URI operation will be accepted, since the client doesn't
538        send the data with the Print-URI operation.  The client SHOULD
539        just issue the Print-URI request.
540
541        Parameters
542        ----------
543        document (file)
544            an open file handler to the document
545        document_name (string)
546            the name of the document
547        document_format (string)
548            the encoding/format of the document
549        document_natural_language (string)
550            if the document is a text file, what language it is in
551        requesting_user_name (string)
552            the user name of the job owner
553        compression (string)
554            the form of compression used on the file
555        job_name (string)
556            the name that the job should be called
557        job_k_octets (int)
558            the size of the job in bytes
559
560        """
561       
562        self.assert_running()
563
564        job_id = self._next_job_id
565        job = GutenbachJob(
566            job_id,
567            creator=requesting_user_name,
568            name=job_name)
569        job.spool(tempfile.TemporaryFile())
570        job.abort()
571        del job
572
573    @sync
574    def get_jobs(self, requesting_user_name=None, which_jobs=None,
575                 requested_attributes=None):
576        """RFC 2911: 3.2.6 Get-Jobs Operation
577       
578        This REQUIRED operation allows a client to retrieve the list
579        of Job objects belonging to the target Printer object. The
580        client may also supply a list of Job attribute names and/or
581        attribute group names. A group of Job object attributes will
582        be returned for each Job object that is returned.
583
584        This operation is similar to the Get-Job-Attributes operation,
585        except that this Get-Jobs operation returns attributes from
586        possibly more than one object.
587
588        Parameters
589        ----------
590        requesting_user_name (string)
591            the user name of the job owner, used as a filter
592        which_jobs (string)
593            a filter for the types of jobs to return:
594              * 'completed' -- only jobs that have finished
595              * 'not-completed' -- processing or pending jobs
596            this defaults to 'not-completed'
597        requested_attributes (list)
598            the job attributes to return
599
600        """
601       
602        self.assert_running()
603
604        # Filter by the which-jobs attribute
605        if which_jobs is None:
606            which_jobs = "not-completed"
607
608        if which_jobs == "completed":
609            jobs = [self.jobs[job_id] for job_id in self.finished_jobs]
610        elif which_jobs == "not-completed":
611            jobs = [self.jobs[job_id] for job_id in self.active_jobs]
612        else:
613            raise ipp.errors.ClientErrorAttributes(
614                which_jobs, ipp.WhichJobs(which_jobs))
615
616        # Filter by username
617        if requesting_user_name is None:
618            user_jobs = jobs
619        else:
620            user_jobs = [job for job in jobs if job.creator == requesting_user_name]
621
622        # Get the attributes of each job
623        job_attrs = [self.get_job_attributes(
624            job.id, requested_attributes=requested_attributes) for job in user_jobs]
625       
626        return job_attrs
627
628    @sync
629    def print_uri(self):
630        """RFC 2911: 3.2.2 Print-URI Operation
631
632        This OPTIONAL operation is identical to the Print-Job
633        operation (section 3.2.1) except that a client supplies a URI
634        reference to the document data using the 'document-uri' (uri)
635        operation attribute (in Group 1) rather than including the
636        document data itself.  Before returning the response, the
637        Printer MUST validate that the Printer supports the retrieval
638        method (e.g., http, ftp, etc.) implied by the URI, and MUST
639        check for valid URI syntax.  If the client-supplied URI scheme
640        is not supported, i.e. the value is not in the Printer
641        object's 'referenced-uri-scheme-supported' attribute, the
642        Printer object MUST reject the request and return the
643        'client-error-uri- scheme-not-supported' status code.
644                                                                             
645        If the Printer object supports this operation, it MUST support
646        the 'reference-uri-schemes-supported' Printer attribute (see
647        section 4.4.27).
648
649        It is up to the IPP object to interpret the URI and
650        subsequently 'pull' the document from the source referenced by
651        the URI string.
652
653        """
654       
655        self.assert_running()
656        # XXX: todo
657
658    @sync
659    def create_job(self, requesting_user_name=None,
660                   job_name=None, job_k_octets=None):
661        """RFC 2911: 3.2.4 Create-Job Operation
662
663        This OPTIONAL operation is similar to the Print-Job operation
664        (section 3.2.1) except that in the Create-Job request, a
665        client does not supply document data or any reference to
666        document data. Also, the client does not supply any of the
667        'document-name', 'document- format', 'compression', or
668        'document-natural-language' operation attributes. This
669        operation is followed by one or more Send-Document or Send-URI
670        operations. In each of those operation requests, the client
671        OPTIONALLY supplies the 'document-name', 'document-format',
672        and 'document-natural-language' attributes for each document
673        in the multi-document Job object.
674
675        Parameters
676        ----------
677        requesting_user_name (string)
678            the user name of the job owner
679        job_name (string)
680            the name that the job should be called
681        job_k_octets (int)
682            the size of the job in bytes
683
684        """
685       
686        self.assert_running()
687
688        job_id = self._next_job_id
689        self._next_job_id += 1
690       
691        job = GutenbachJob(
692            job_id,
693            creator=requesting_user_name,
694            name=job_name)
695
696        self.jobs[job_id] = job
697        return job_id
698
699    @sync
700    def pause_printer(self):
701        """RFC 2911: 3.2.7 Pause-Printer Operation
702
703        This OPTIONAL operation allows a client to stop the Printer
704        object from scheduling jobs on all its devices.  Depending on
705        implementation, the Pause-Printer operation MAY also stop the
706        Printer from processing the current job or jobs.  Any job that
707        is currently being printed is either stopped as soon as the
708        implementation permits or is completed, depending on
709        implementation.  The Printer object MUST still accept create
710        operations to create new jobs, but MUST prevent any jobs from
711        entering the 'processing' state.
712
713        If the Pause-Printer operation is supported, then the
714        Resume-Printer operation MUST be supported, and vice-versa.
715
716        The IPP Printer MUST accept the request in any state and
717        transition the Printer to the indicated new 'printer-state'
718        before returning as follows:
719
720        Current       New         Reasons             Reponse
721        --------------------------------------------------------------
722        'idle'       'stopped'    'paused'            'successful-ok'
723        'processing' 'processing' 'moving-to-paused'  'successful-ok'
724        'processing' 'stopped'    'paused'            'successful-ok'
725        'stopped'    'stopped'    'paused'            'successful-ok'
726
727        """
728       
729        self.assert_running()
730        if not self.paused:
731            if self.current_job is not None and self.current_job.is_playing:
732                self.current_job.pause()
733            self.paused = True
734            logger.info("%s paused", str(self))
735
736    @sync
737    def resume_printer(self):
738        """RFC 2911: 3.2.8 Resume-Printer Operation
739
740        This operation allows a client to resume the Printer object
741        scheduling jobs on all its devices.  The Printer object MUST
742        remove the 'paused' and 'moving-to-paused' values from the
743        Printer object's 'printer-state-reasons' attribute, if
744        present.  If there are no other reasons to keep a device
745        paused (such as media-jam), the IPP Printer is free to
746        transition itself to the 'processing' or 'idle' states,
747        depending on whether there are jobs to be processed or not,
748        respectively, and the device(s) resume processing jobs.
749
750        If the Pause-Printer operation is supported, then the
751        Resume-Printer operation MUST be supported, and vice-versa.
752
753        The IPP Printer removes the 'printer-stopped' value from any
754        job's 'job-state-reasons' attributes contained in that
755        Printer.
756
757        The IPP Printer MUST accept the request in any state,
758        transition the Printer object to the indicated new state as
759        follows:
760
761        Current       New           Response
762        ---------------------------------------------
763        'idle'       'idle'         'successful-ok'
764        'processing' 'processing'   'successful-ok'
765        'stopped'    'processing'   'successful-ok'
766        'stopped'    'idle'         'successful-ok'
767
768        """
769       
770        self.assert_running()
771        if self.paused:
772            if self.current_job is not None:
773                self.current_job.resume()
774            self.paused = False
775            logger.info("%s unpaused", str(self))
776
777    @sync
778    def get_printer_attributes(self, requested_attributes=None):
779        """RFC 2911: 3.2.5 Get-Printer-Attributes Operation
780
781        This REQUIRED operation allows a client to request the values
782        of the attributes of a Printer object.
783       
784        In the request, the client supplies the set of Printer
785        attribute names and/or attribute group names in which the
786        requester is interested. In the response, the Printer object
787        returns a corresponding attribute set with the appropriate
788        attribute values filled in.
789
790        Parameters
791        ----------
792        requested_attributes (list)
793            the attributes to return
794
795        """
796       
797        self.assert_running()
798        if requested_attributes is None:
799            requested = self.printer_attributes
800        else:
801            requested = [a for a in self.printer_attributes \
802                         if a in requested_attributes]
803
804        _attributes = [attr.replace("-", "_") for attr in requested]
805        attributes = [getattr(self, attr) for attr in _attributes]
806        return attributes
807
808    @sync
809    def set_printer_attributes(self, attributes):
810        self.assert_running()
811        for attr in attributes:
812            try:
813                setattr(self, attr, attributes[attr])
814            except AttributeError:
815                raise ipp.errors.ClientErrorAttributes
816
817    @sync
818    def cancel_job(self, job_id, requesting_user_name=None):
819        """RFC 2911: 3.3.3 Cancel-Job Operation
820
821        This REQUIRED operation allows a client to cancel a Print Job
822        from the time the job is created up to the time it is
823        completed, canceled, or aborted. Since a Job might already be
824        printing by the time a Cancel-Job is received, some media
825        sheet pages might be printed before the job is actually
826        terminated.
827
828        The IPP object MUST accept or reject the request based on the
829        job's current state and transition the job to the indicated
830        new state as follows:
831
832        Current State       New State           Response
833        -----------------------------------------------------------------
834        pending             canceled            successful-ok
835        pending-held        canceled            successful-ok
836        processing          canceled            successful-ok
837        processing          processing          successful-ok               See Rule 1
838        processing          processing          client-error-not-possible   See Rule 2
839        processing-stopped  canceled            successful-ok
840        processing-stopped  processing-stopped  successful-ok               See Rule 1
841        processing-stopped  processing-stopped  client-error-not-possible   See Rule 2
842        completed           completed           client-error-not-possible
843        canceled            canceled            client-error-not-possible
844        aborted             aborted             client-error-not-possible
845
846        Rule 1: If the implementation requires some measurable time to
847        cancel the job in the 'processing' or 'processing-stopped' job
848        states, the IPP object MUST add the 'processing-to-stop-point'
849        value to the job's 'job-state-reasons' attribute and then
850        transition the job to the 'canceled' state when the processing
851        ceases (see section 4.3.8).
852
853        Rule 2: If the Job object already has the
854        'processing-to-stop-point' value in its 'job-state-reasons'
855        attribute, then the Printer object MUST reject a Cancel-Job
856        operation.
857
858        Parameters
859        ----------
860        job_id (integer)
861            the id of the job to cancel
862        requesting_user_name (string)
863            the name of the job's owner
864
865        """
866
867        self.assert_running()
868        job = self.get_job(job_id)
869        try:
870            job.cancel()
871        except InvalidJobStateException:
872            # XXX
873            raise
874
875    @sync
876    def send_document(self, job_id, document, document_name=None,
877                      document_format=None, document_natural_language=None,
878                      requesting_user_name=None, compression=None,
879                      last_document=None):
880        """RFC 2911: 3.3.1 Send-Document Operation
881       
882        This OPTIONAL operation allows a client to create a
883        multi-document Job object that is initially 'empty' (contains
884        no documents). In the Create-Job response, the Printer object
885        returns the Job object's URI (the 'job-uri' attribute) and the
886        Job object's 32-bit identifier (the 'job-id' attribute). For
887        each new document that the client desires to add, the client
888        uses a Send-Document operation. Each Send- Document Request
889        contains the entire stream of document data for one document.
890
891        If the Printer supports this operation but does not support
892        multiple documents per job, the Printer MUST reject subsequent
893        Send-Document operations supplied with data and return the
894        'server-error-multiple- document-jobs-not-supported'. However,
895        the Printer MUST accept the first document with a 'true' or
896        'false' value for the 'last-document' operation attribute (see
897        below), so that clients MAY always submit one document jobs
898        with a 'false' value for 'last-document' in the first
899        Send-Document and a 'true' for 'last-document' in the second
900        Send-Document (with no data).
901       
902        Since the Create-Job and the send operations (Send-Document or
903        Send- URI operations) that follow could occur over an
904        arbitrarily long period of time for a particular job, a client
905        MUST send another send operation within an IPP Printer defined
906        minimum time interval after the receipt of the previous
907        request for the job. If a Printer object supports the
908        Create-Job and Send-Document operations, the Printer object
909        MUST support the 'multiple-operation-time-out' attribute (see
910        section 4.4.31). This attribute indicates the minimum number
911        of seconds the Printer object will wait for the next send
912        operation before taking some recovery action.
913
914        An IPP object MUST recover from an errant client that does not
915        supply a send operation, sometime after the minimum time
916        interval specified by the Printer object's
917        'multiple-operation-time-out' attribute.
918
919        Parameters
920        ----------
921        job_id (integer)
922            the id of the job to send the document
923        document (file)
924            an open file handler to the document
925        document_name (string)
926            the name of the document
927        document_format (string)
928            the encoding/format of the document
929        document_natural_language (string)
930            if the document is a text file, what language it is in
931        requesting_user_name (string)
932            the user name of the job owner
933        compression (string)
934            the form of compression used on the file
935        last_document (boolean)
936            whether or not this is the last document in this job
937
938        """
939       
940        self.assert_running()
941        job = self.get_job(job_id)
942        job.spool(document)
943        if 'dryrun' in self.config and self.config['dryrun']:
944            job.player._dryrun = True
945        self.pending_jobs.append(job_id)
946       
947    @sync
948    def send_uri(self, job_id, document_uri, document_name=None,
949                 document_format=None, document_natural_language=None,
950                 requesting_user_name=None, compression=None,
951                 last_document=None):
952        """RFC 2911: 3.2.2 Send URI
953
954        This OPTIONAL operation is identical to the Send-Document
955        operation (see section 3.3.1) except that a client MUST supply
956        a URI reference ('document-uri' operation attribute) rather
957        than the document data itself.  If a Printer object supports
958        this operation, clients can use both Send-URI or Send-Document
959        operations to add new documents to an existing multi-document
960        Job object.  However, if a client needs to indicate that the
961        previous Send-URI or Send-Document was the last document, the
962        client MUST use the Send-Document operation with no document
963        data and the 'last-document' flag set to 'true' (rather than
964        using a Send-URI operation with no 'document-uri' operation
965        attribute).
966
967        If a Printer object supports this operation, it MUST also
968        support the Print-URI operation (see section 3.2.2).
969
970        The Printer object MUST validate the syntax and URI scheme of
971        the supplied URI before returning a response, just as in the
972        Print-URI operation.  The IPP Printer MAY validate the
973        accessibility of the document as part of the operation or
974        subsequently (see section 3.2.2).
975
976        Parameters
977        ----------
978        job_id (integer)
979            the id of the job to send the uri
980        document_uri (string)
981            the uri of the document
982        document_name (string)
983            the name of the document
984        document_format (string)
985            the encoding/format of the document
986        document_natural_language (string)
987            if the document is a text file, what language it is in
988        requesting_user_name (string)
989            the user name of the job owner
990        compression (string)
991            the form of compression used on the file
992        last_document (boolean)
993            whether or not this is the last document in this job
994
995        """
996
997        self.assert_running()
998        job = self.get_job(job_id)
999        # XXX: need to validate URI
1000        # XXX: need to deal with the URI stream?
1001
1002        #job.spool_uri(document_uri)
1003        #if 'dryrun' in self.config and self.config['dryrun']:
1004        #    job.player._dryrun = True
1005        #self.pending_jobs.append(job_id)
1006       
1007    @sync
1008    def get_job_attributes(self, job_id, requested_attributes=None):
1009        """RFC 2911: 3.3.4 Get-Job-Attributes Operation
1010
1011        This REQUIRED operation allows a client to request the values
1012        of attributes of a Job object and it is almost identical to
1013        the Get- Printer-Attributes operation (see section 3.2.5). The
1014        only differences are that the operation is directed at a Job
1015        object rather than a Printer object, there is no
1016        'document-format' operation attribute used when querying a Job
1017        object, and the returned attribute group is a set of Job
1018        object attributes rather than a set of Printer object
1019        attributes.
1020
1021        For Jobs, the possible names of attribute groups are:
1022          - 'job-template': the subset of the Job Template attributes
1023            that apply to a Job object (the first column of the table
1024            in Section 4.2) that the implementation supports for Job
1025            objects.
1026          - 'job-description': the subset of the Job Description
1027            attributes specified in Section 4.3 that the
1028            implementation supports for Job objects.
1029          - 'all': the special group 'all' that includes all
1030            attributes that the implementation supports for Job
1031            objects.
1032
1033        Since a client MAY request specific attributes or named
1034        groups, there is a potential that there is some overlap. For
1035        example, if a client requests, 'job-name' and
1036        'job-description', the client is actually requesting the
1037        'job-name' attribute once by naming it explicitly, and once by
1038        inclusion in the 'job-description' group. In such cases, the
1039        Printer object NEED NOT return the attribute only once in the
1040        response even if it is requested multiple times. The client
1041        SHOULD NOT request the same attribute in multiple ways.
1042
1043        It is NOT REQUIRED that a Job object support all attributes
1044        belonging to a group (since some attributes are
1045        OPTIONAL). However it is REQUIRED that each Job object support
1046        all these group names.
1047
1048        Parameters
1049        ----------
1050        job_id (integer)
1051            the id of the job to send the uri
1052        requested_attributes (list)
1053            the attributes to return
1054
1055        """
1056
1057        self.assert_running()
1058        if requested_attributes is None:
1059            requested = self.job_attributes
1060        else:
1061            requested = [a for a in self.job_attributes \
1062                         if a in requested_attributes]
1063
1064        _attributes = [attr.replace("-", "_") for attr in requested]
1065        attributes = [getattr(self, attr)(job_id) for attr in _attributes]
1066        return attributes
1067
1068    @sync
1069    def set_job_attributes(self, job_id, attributes):
1070        self.assert_running()
1071        job = self.get_job(job_id)
1072        for attr in attributes:
1073            if attr in ("job-id", "job-k-octets", "job-state", "job-printer-uri"):
1074                raise ipp.errors.ClientErrorAttributesNotSettable(attr)
1075            elif attr == "job-name":
1076                job.name = attributes[attr]
1077            elif attr == "job-originating-user-name":
1078                job.creator = attributes[attr] # XXX: do we want this?
1079
1080    @sync
1081    def restart_job(self, job_id, requesting_user_name=None):
1082        self.assert_running()
1083        job = self.get_job(job_id)
1084        try:
1085            job.restart()
1086        except InvalidJobStateException:
1087            # XXX
1088            raise ipp.errors.ClientErrorNotPossible
1089
1090        self.finished_jobs.remove(job_id)
1091        self.pending_jobs.append(job_id)
1092
1093    @sync
1094    def promote_job(self, job_id, requesting_user_name=None):
1095        # According to RFC 3998, we need to put the job at the front
1096        # of the queue (so that when the currently playing job
1097        # completes, this one will go next
1098       
1099        self.assert_running()
1100        job = self.get_job(job_id)
1101        job.priority = 1 # XXX we need to actually do something
1102                         # correct here
Note: See TracBrowser for help on using the repository browser.