source: server/lib/gutenbach/server/printer.py @ 57bc2dc

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

Add support for print-job and verify-job in printer.py

  • Property mode set to 100644
File size: 18.2 KB
RevLine 
[33ea505]1from .errors import InvalidJobException, InvalidPrinterStateException, InvalidJobStateException
2from .job import GutenbachJob
[b01b6d1]3from gutenbach.ipp import PrinterStates as States
[b2e077a]4import gutenbach.ipp as ipp
5import logging
6import time
[eee389a]7import threading
8import heapq
9import traceback
10import sys
[57bc2dc]11import tempfile
[cf0d7e8]12from . import sync
[eee389a]13
[d04a689]14
15# initialize logger
16logger = logging.getLogger(__name__)
[776a659]17
[eee389a]18class GutenbachPrinter(threading.Thread):
[b2e077a]19
[1a63bf7]20    # for IPP
[33ea505]21    printer_attributes = [
[b2e077a]22        "printer-uri-supported",
23        "uri-authentication-supported",
24        "uri-security-supported",
25        "printer-name",
26        "printer-state",
27        "printer-state-reasons",
28        "ipp-versions-supported",
29        "operations-supported",
30        "charset-configured",
31        "charset-supported",
32        "natural-language-configured",
33        "generated-natural-language-supported",
34        "document-format-default",
35        "document-format-supported",
36        "printer-is-accepting-jobs",
37        "queued-job-count",
38        "pdl-override-supported",
39        "printer-up-time",
[f6e2532]40        "compression-supported",
41        "multiple-operation-time-out",
42        "multiple-document-jobs-supported",
[1a63bf7]43    ]
[b2e077a]44
[33ea505]45    job_attributes = [
46        "job-id",
47        "job-name",
48        "job-originating-user-name",
49        "job-k-octets",
50        "job-state",
51        "job-printer-uri"
52    ]
53
[f6e2532]54    operations = [
55        "print-job",
[33ea505]56        "validate-job",
[f6e2532]57        "get-jobs",
[33ea505]58        "print-uri",
59        "create-job",
60        "pause-printer",
61        "resume-printer",
62        "get-printer-attributes",
63        "set-printer-attributes",
64        "cancel-job",
65        "send-document",
66        "send-uri",
67        "get-job-attributes",
68        "set-job-attributes",
69        "restart-job",
70        "promote-job"
[f6e2532]71    ]
72       
[609a9b0]73    def __init__(self, name, config, *args, **kwargs):
[eee389a]74
[d21198f]75        super(GutenbachPrinter, self).__init__(*args, **kwargs)
76       
77        self.name = name
[609a9b0]78        self.config = config
[d21198f]79        self.time_created = int(time.time())
[b2e077a]80
[d21198f]81        self.finished_jobs = []
82        self.pending_jobs = []
83        self.current_job = None
84        self.jobs = {}
[b2e077a]85
[d21198f]86        self.lock = threading.RLock()
87        self.running = False
88        self.paused = False
[776a659]89
[d21198f]90        # CUPS ignores jobs with id 0, so we have to start at 1
91        self._next_job_id = 1
[b01b6d1]92
93    def __repr__(self):
94        return str(self)
95
96    def __str__(self):
97        return "<Printer '%s'>" % self.name
98
[33ea505]99    def run(self):
100        self.running = True
101        while self.running:
102            with self.lock:
103                try:
[fa3e2c6]104                    if not self.paused and self.current_job is None:
[33ea505]105                        self.start_job()
[345c476]106                    elif self.current_job.is_done:
[33ea505]107                        self.complete_job()
108                except:
109                    logger.fatal(traceback.format_exc())
110                    sys.exit(1)
111            time.sleep(0.1)
112
[b01b6d1]113    ######################################################################
114    ###                          Properties                            ###
115    ######################################################################
116
117    @property
118    def uris(self):
119        uris = ["ipp://localhost:8000/printers/" + self.name,
120                "ipp://localhost/printers/" + self.name]
121        return uris
122   
123    @property
124    def uri(self):
125        return self.uris[0]
126
127    @property
[cf0d7e8]128    @sync
[eee389a]129    def state(self):
[cf0d7e8]130        if self.current_job is not None:
131            return States.PROCESSING
132        elif len(self.pending_jobs) == 0:
133            return States.IDLE
134        else:
135            return States.STOPPED
[eee389a]136
137    @property
[cf0d7e8]138    @sync
[eee389a]139    def active_jobs(self):
[cf0d7e8]140        jobs = self.pending_jobs[:]
141        if self.current_job is not None:
142            jobs.insert(0, self.current_job.id)
[eee389a]143        return jobs
[b01b6d1]144
145    ######################################################################
146    ###                            Methods                             ###
147    ######################################################################
148
[cf0d7e8]149    @sync
[eee389a]150    def start_job(self):
[cf0d7e8]151        if self.current_job is None:
152            try:
153                job_id = heapq.heappop(self.pending_jobs)
154                self.current_job = self.get_job(job_id)
155                self.current_job.play()
156            except IndexError:
157                self.current_job = None
158            except InvalidJobStateException:
159                heapq.heappush(self.pending_jobs, self.current_job.id)
160                self.current_job = None
[eee389a]161                   
[cf0d7e8]162    @sync
[eee389a]163    def complete_job(self):
[cf0d7e8]164        if self.current_job is None:
165            return
[eee389a]166
[cf0d7e8]167        try:
168            if not self.current_job.is_done:
169                self.current_job.stop()
170        finally:
171            self.finished_jobs.append(self.current_job.id)
172            self.current_job = None
[1a63bf7]173
[cf0d7e8]174    @sync
[eee389a]175    def get_job(self, job_id):
[cf0d7e8]176        if job_id not in self.jobs:
177            raise InvalidJobException(job_id)
178        return self.jobs[job_id]
[b01b6d1]179
180    ######################################################################
181    ###                        IPP Attributes                          ###
182    ######################################################################
[1a63bf7]183
[b2e077a]184    @property
185    def printer_uri_supported(self):
[793432f]186        return ipp.PrinterUriSupported(self.uri)
[9da7428]187    @printer_uri_supported.setter
188    def printer_uri_supported(self, val):
189        raise ipp.errors.AttributesNotSettable("printer-uri-supported")
[1a63bf7]190
[b2e077a]191    @property
192    def uri_authentication_supported(self):
[793432f]193        return ipp.UriAuthenticationSupported("none")
[9da7428]194    @uri_authentication_supported.setter
195    def uri_authentication_supported(self, val):
196        raise ipp.errors.AttributesNotSettable("uri-authentication-supported")
[b2e077a]197
198    @property
199    def uri_security_supported(self):
[793432f]200        return ipp.UriSecuritySupported("none")
[9da7428]201    @uri_security_supported.setter
202    def uri_security_supported(self, val):
203        raise ipp.errors.AttributesNotSettable("uri-security-supported")
[b2e077a]204
205    @property
206    def printer_name(self):
[793432f]207        return ipp.PrinterName(self.name)
[9da7428]208    @printer_name.setter
209    def printer_name(self, val):
210        raise ipp.errors.AttributesNotSettable("printer-name")
[1a63bf7]211
[b2e077a]212    @property
213    def printer_state(self):
[b01b6d1]214        return ipp.PrinterState(self.state)
[9da7428]215    @printer_state.setter
216    def printer_state(self, val):
217        raise ipp.errors.AttributesNotSettable("printer-state")
[1a63bf7]218
[b2e077a]219    @property
220    def printer_state_reasons(self):
[793432f]221        return ipp.PrinterStateReasons("none")
[9da7428]222    @printer_state_reasons.setter
223    def printer_state_reasons(self, val):
224        raise ipp.errors.AttributesNotSettable("printer-state-reasons")
[b2e077a]225
226    @property
227    def ipp_versions_supported(self):
[609a9b0]228        return ipp.IppVersionsSupported(*self.config['ipp-versions'])
[9da7428]229    @ipp_versions_supported.setter
230    def ipp_versions_supported(self, val):
231        raise ipp.errors.AttributesNotSettable("ipp-versions-supported")
[1a63bf7]232
[f6e2532]233    # XXX: We should query ourself for the supported operations
[b2e077a]234    @property
235    def operations_supported(self):
[793432f]236        return ipp.OperationsSupported(ipp.OperationCodes.GET_JOBS)
[9da7428]237    @operations_supported.setter
238    def operations_supported(self, val):
239        raise ipp.errors.AttributesNotSettable("operations-supported")
[b2e077a]240
241    @property
242    def charset_configured(self):
[9da7428]243        return ipp.CharsetConfigured("utf-8") # XXX
244    @charset_configured.setter
245    def charset_configured(self, val):
246        raise ipp.errors.AttributesNotSettable("charset-configured")
247       
[b2e077a]248    @property
249    def charset_supported(self):
[9da7428]250        return ipp.CharsetSupported("utf-8") # XXX
251    @charset_supported.setter
252    def charset_supported(self, val):
253        raise ipp.errors.AttributesNotSettable("charset-supported")
[b2e077a]254
255    @property
256    def natural_language_configured(self):
[793432f]257        return ipp.NaturalLanguageConfigured("en-us")
[9da7428]258    @natural_language_configured.setter
259    def natural_language_configured(self, val):
260        raise ipp.errors.AttributesNotSettable("natural-language-configured")
[b2e077a]261
262    @property
263    def generated_natural_language_supported(self):
[793432f]264        return ipp.GeneratedNaturalLanguageSupported("en-us")
[9da7428]265    @generated_natural_language_supported.setter
266    def generated_natural_language_supported(self, val):
267        raise ipp.errors.AttributesNotSettable("generated-natural-language-supported")
[b2e077a]268
269    @property
270    def document_format_default(self):
[793432f]271        return ipp.DocumentFormatDefault("application/octet-stream")
[9da7428]272    @document_format_default.setter
273    def document_format_default(self, val):
274        raise ipp.errors.AttributesNotSettable("document-format-default")
[b2e077a]275
276    @property
277    def document_format_supported(self):
[793432f]278        return ipp.DocumentFormatSupported("application/octet-stream", "audio/mp3")
[9da7428]279    @document_format_supported.setter
280    def document_format_supported(self, val):
281        raise ipp.errors.AttributesNotSettable("document-format-supported")
[b2e077a]282
283    @property
284    def printer_is_accepting_jobs(self):
[793432f]285        return ipp.PrinterIsAcceptingJobs(True)
[9da7428]286    @printer_is_accepting_jobs.setter
287    def printer_is_accepting_jobs(self, val):
288        raise ipp.errors.AttributesNotSettable("printer-is-accepting-jobs")
[b2e077a]289
290    @property
291    def queued_job_count(self):
[793432f]292        return ipp.QueuedJobCount(len(self.active_jobs))
[9da7428]293    @queued_job_count.setter
294    def queued_job_count(self, val):
295        raise ipp.errors.AttributesNotSettable("queued-job-count")
[b2e077a]296
297    @property
298    def pdl_override_supported(self):
[793432f]299        return ipp.PdlOverrideSupported("not-attempted")
[9da7428]300    @pdl_override_supported.setter
301    def pdl_override_supported(self, val):
302        raise ipp.errors.AttributesNotSettable("pdl-override-supported")
[b2e077a]303
304    @property
305    def printer_up_time(self):
[793432f]306        return ipp.PrinterUpTime(int(time.time()) - self.time_created)
[9da7428]307    @printer_up_time.setter
308    def printer_up_time(self, val):
309        raise ipp.errors.AttributesNotSettable("printer-up-time")
[b2e077a]310
311    @property
312    def compression_supported(self):
[793432f]313        return ipp.CompressionSupported("none")
[9da7428]314    @compression_supported.setter
315    def compression_supported(self, val):
316        raise ipp.errors.AttributesNotSettable("compression-supported")
[b2e077a]317
[f6e2532]318    @property
319    def multiple_operation_time_out(self):
[793432f]320        return ipp.MultipleOperationTimeOut(240)
[9da7428]321    @multiple_operation_time_out.setter
322    def multiple_operation_time_out(self, val):
323        raise ipp.errors.AttributesNotSettable("multiple-operation-time-out")
[f6e2532]324
325    @property
326    def multiple_document_jobs_supported(self):
[793432f]327        return ipp.MultipleDocumentJobsSupported(False)
[9da7428]328    @multiple_document_jobs_supported.setter
329    def multiple_document_jobs_supported(self, val):
330        raise ipp.errors.AttributesNotSettable("multiple-document-jobs-supported")
[f6e2532]331
[33ea505]332    ######################################################################
333    ###                      Job IPP Attributes                        ###
334    ######################################################################
335
336    def job_id(self, job_id):
337        job = self.get_job(job_id)
338        return ipp.JobId(job.id)
339
340    def job_name(self, job_id):
341        job = self.get_job(job_id)
342        return ipp.JobName(job.name)
343
344    def job_originating_user_name(self, job_id):
345        job = self.get_job(job_id)
346        return ipp.JobOriginatingUserName(job.creator)
347
348    def job_k_octets(self, job_id):
349        job = self.get_job(job_id)
350        return ipp.JobKOctets(job.size)
351
352    def job_state(self, job_id):
353        job = self.get_job(job_id)
354        return ipp.JobState(job.state)
355
356    def job_printer_uri(self, job_id):
357        job = self.get_job(job_id)
358        return ipp.JobPrinterUri(self.uri)
[ee8e6d0]359
[b01b6d1]360    ######################################################################
361    ###                        IPP Operations                          ###
362    ######################################################################
363
[57bc2dc]364    def print_job(self, document, document_name=None, document_format=None,
365                  document_natural_language=None, requesting_user_name=None,
366                  compression=None, job_name=None, job_k_octets=None):
[b01b6d1]367
[57bc2dc]368        # create the job
369        job_id = self.create_job(
370            requesting_user_name=requesting_user_name,
371            job_name=job_name,
372            job_k_octets=job_k_octets)
373       
374        # send the document
375        self.send_document(
376            job_id,
377            document,
378            document_name=document_name,
379            document_format=document_format,
380            document_natural_language=document_natural_language,
381            requesting_user_name=requesting_user_name,
382            compression=compression,
383            last_document=False)
384
385        return job_id
386
387    def verify_job(self, document_name=None, document_format=None,
388                  document_natural_language=None, requesting_user_name=None,
389                  compression=None, job_name=None, job_k_octets=None):
390
391        job_id = self._next_job_id
392        job = GutenbachJob(
393            job_id,
394            creator=requesting_user_name,
395            name=job_name)
396        job.spool(tempfile.TemporaryFile())
397        job.abort()
398        del job
[b01b6d1]399
[33ea505]400    def get_jobs(self, requesting_user_name=None, which_jobs=None,
401                 requested_attributes=None):
402       
[b01b6d1]403        # Filter by the which-jobs attribute
404        if which_jobs is None:
[34a4e5d]405            which_jobs = "not-completed"
406
407        if which_jobs == "completed":
[b01b6d1]408            jobs = [self.jobs[job_id] for job_id in self.finished_jobs]
409        elif which_jobs == "not-completed":
410            jobs = [self.jobs[job_id] for job_id in self.active_jobs]
[ee8e6d0]411        else:
[b01b6d1]412            raise ipp.errors.ClientErrorAttributes(
413                which_jobs, ipp.WhichJobs(which_jobs))
[b2e077a]414
[b01b6d1]415        # Filter by username
416        if requesting_user_name is None:
417            user_jobs = jobs
418        else:
419            user_jobs = [job for job in jobs if job.creator == requesting_user_name]
[33ea505]420
421        # Get the attributes of each job
422        job_attrs = [self.get_job_attributes(
423            job.id, requested_attributes=requested_attributes) for job in user_jobs]
[ee8e6d0]424       
[33ea505]425        return job_attrs
[ee8e6d0]426
[b01b6d1]427    def print_uri(self):
428        pass
429
[57bc2dc]430    def create_job(self, requesting_user_name=None, job_name=None,
431                   job_k_octets=None):
432
[eee389a]433        job_id = self._next_job_id
434        self._next_job_id += 1
[ee8e6d0]435       
[33ea505]436        job = GutenbachJob(
437            job_id,
438            creator=requesting_user_name,
439            name=job_name)
[57bc2dc]440
[ee8e6d0]441        self.jobs[job_id] = job
[eee389a]442        self.pending_jobs.append(job_id)
[b01b6d1]443       
[33ea505]444        return job_id
[776a659]445
[fa3e2c6]446    @sync
[b01b6d1]447    def pause_printer(self):
[fa3e2c6]448        """Pause the printer.
449
450        Does nothing if the printer is already paused.
451        """
452        if self.paused:
453            return
454
455        if self.current_job is not None and self.current_job.is_playing:
456            self.current_job.pause()
457
458        self.paused = True
459
460    @sync
[b01b6d1]461    def resume_printer(self):
[fa3e2c6]462        """Resume the printer.
463
464        Does nothing if the printer is not paused.
465        """
466        if not self.paused:
467            return
468
469        if self.current_job is not None:
470            self.current_job.resume()
471
472        self.paused = False
[776a659]473
[57bc2dc]474    @sync
[b01b6d1]475    def get_printer_attributes(self, requested_attributes=None):
476        if requested_attributes is None:
[33ea505]477            requested = self.printer_attributes
[e58af05]478        else:
[33ea505]479            requested = [a for a in self.printer_attributes \
480                         if a in requested_attributes]
[b2e077a]481
[b01b6d1]482        _attributes = [attr.replace("-", "_") for attr in requested]
483        attributes = [getattr(self, attr) for attr in _attributes]
484        return attributes
[776a659]485
[57bc2dc]486    @sync
[9da7428]487    def set_printer_attributes(self, job_id, attributes):
488        for attr in attributes:
489            try:
490                setattr(self, attr, attributes[attr])
491            except AttributeError:
492                raise ipp.errors.ClientErrorAttributes
[33ea505]493
[57bc2dc]494    @sync
[33ea505]495    def cancel_job(self, job_id, requesting_user_name=None):
496        job = self.get_job(job_id)
497        try:
498            job.cancel()
499        except InvalidJobStateException:
500            # XXX
501            raise
502
[57bc2dc]503    @sync
[33ea505]504    def send_document(self, job_id, document, document_name=None,
505                      document_format=None, document_natural_language=None,
506                      requesting_user_name=None, compression=None,
507                      last_document=None):
508
509        job = self.get_job(job_id)
[345c476]510        job.spool(document)
[33ea505]511
[57bc2dc]512    @sync
[c1dc25f]513    def send_uri(self, job_id, document_uri, document_name=None,
514                 document_format=None, document_natural_language=None,
515                 requesting_user_name=None, compression=None,
516                 last_document=None):
517        job = self.get_job(job_id)
518        # XXX: need to validate URI
519        # XXX: need to deal with the URI stream?
520        #job.spool_uri(document_uri)
[33ea505]521
[57bc2dc]522    @sync
[33ea505]523    def get_job_attributes(self, job_id, requested_attributes=None):
524        if requested_attributes is None:
525            requested = self.job_attributes
526        else:
527            requested = [a for a in self.job_attributes \
528                         if a in requested_attributes]
529
530        _attributes = [attr.replace("-", "_") for attr in requested]
531        attributes = [getattr(self, attr)(job_id) for attr in _attributes]
532        return attributes
533
[57bc2dc]534    @sync
[9da7428]535    def set_job_attributes(self, job_id, attributes):
536        job = self.get_job(job_id)
537        for attr in attributes:
538            if attr in ("job-id", "job-k-octets", "job-state", "job-printer-uri"):
539                raise ipp.errors.ClientErrorAttributesNotSettable(attr)
540            elif attr == "job-name":
541                job.name = attributes[attr]
542            elif attr == "job-originating-user-name":
543                job.creator = attributes[attr] # XXX: do we want this?
[57bc2dc]544
545    @sync
[c1cebbc]546    def restart_job(self, job_id, requesting_user_name=None):
547        job = self.get_job(job_id)
548        try:
549            job.restart()
550        except InvalidJobStateException:
551            # XXX
552            raise ipp.errors.ClientErrorNotPossible
[33ea505]553
[57bc2dc]554        self.finished_jobs.remove(job_id)
555        self.pending_jobs.append(job_id)
[33ea505]556
[57bc2dc]557    @sync
[c1cebbc]558    def promote_job(self, job_id, requesting_user_name=None):
[c500bc2]559        # According to RFC 3998, we need to put the job at the front
560        # of the queue (so that when the currently playing job
561        # completes, this one will go next
562       
563        job = self.get_job(job_id)
564        job.priority = 1 # XXX we need to actually do something
565                         # correct here
Note: See TracBrowser for help on using the repository browser.