source: server/lib/gutenbach/server/printer.py @ 7e29e6a

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

Fix a few small bugs in printer.py

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