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
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    def __repr__(self):
94        return str(self)
95
96    def __str__(self):
97        return "<Printer '%s'>" % self.name
98
99    def run(self):
100        self.running = True
101        while self.running:
102            with self.lock:
103                try:
104                    if not self.paused and self.current_job is None:
105                        self.start_job()
106                    elif self.current_job.is_done:
107                        self.complete_job()
108                except:
109                    logger.fatal(traceback.format_exc())
110                    sys.exit(1)
111            time.sleep(0.1)
112
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
128    @sync
129    def state(self):
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
136
137    @property
138    @sync
139    def active_jobs(self):
140        jobs = self.pending_jobs[:]
141        if self.current_job is not None:
142            jobs.insert(0, self.current_job.id)
143        return jobs
144
145    ######################################################################
146    ###                            Methods                             ###
147    ######################################################################
148
149    @sync
150    def start_job(self):
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
161                   
162    @sync
163    def complete_job(self):
164        if self.current_job is None:
165            return
166
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
173
174    @sync
175    def get_job(self, job_id):
176        if job_id not in self.jobs:
177            raise InvalidJobException(job_id)
178        return self.jobs[job_id]
179
180    ######################################################################
181    ###                        IPP Attributes                          ###
182    ######################################################################
183
184    @property
185    def printer_uri_supported(self):
186        return ipp.PrinterUriSupported(self.uri)
187    @printer_uri_supported.setter
188    def printer_uri_supported(self, val):
189        raise ipp.errors.AttributesNotSettable("printer-uri-supported")
190
191    @property
192    def uri_authentication_supported(self):
193        return ipp.UriAuthenticationSupported("none")
194    @uri_authentication_supported.setter
195    def uri_authentication_supported(self, val):
196        raise ipp.errors.AttributesNotSettable("uri-authentication-supported")
197
198    @property
199    def uri_security_supported(self):
200        return ipp.UriSecuritySupported("none")
201    @uri_security_supported.setter
202    def uri_security_supported(self, val):
203        raise ipp.errors.AttributesNotSettable("uri-security-supported")
204
205    @property
206    def printer_name(self):
207        return ipp.PrinterName(self.name)
208    @printer_name.setter
209    def printer_name(self, val):
210        raise ipp.errors.AttributesNotSettable("printer-name")
211
212    @property
213    def printer_state(self):
214        return ipp.PrinterState(self.state)
215    @printer_state.setter
216    def printer_state(self, val):
217        raise ipp.errors.AttributesNotSettable("printer-state")
218
219    @property
220    def printer_state_reasons(self):
221        return ipp.PrinterStateReasons("none")
222    @printer_state_reasons.setter
223    def printer_state_reasons(self, val):
224        raise ipp.errors.AttributesNotSettable("printer-state-reasons")
225
226    @property
227    def ipp_versions_supported(self):
228        return ipp.IppVersionsSupported(*self.config['ipp-versions'])
229    @ipp_versions_supported.setter
230    def ipp_versions_supported(self, val):
231        raise ipp.errors.AttributesNotSettable("ipp-versions-supported")
232
233    # XXX: We should query ourself for the supported operations
234    @property
235    def operations_supported(self):
236        return ipp.OperationsSupported(ipp.OperationCodes.GET_JOBS)
237    @operations_supported.setter
238    def operations_supported(self, val):
239        raise ipp.errors.AttributesNotSettable("operations-supported")
240
241    @property
242    def charset_configured(self):
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       
248    @property
249    def charset_supported(self):
250        return ipp.CharsetSupported("utf-8") # XXX
251    @charset_supported.setter
252    def charset_supported(self, val):
253        raise ipp.errors.AttributesNotSettable("charset-supported")
254
255    @property
256    def natural_language_configured(self):
257        return ipp.NaturalLanguageConfigured("en-us")
258    @natural_language_configured.setter
259    def natural_language_configured(self, val):
260        raise ipp.errors.AttributesNotSettable("natural-language-configured")
261
262    @property
263    def generated_natural_language_supported(self):
264        return ipp.GeneratedNaturalLanguageSupported("en-us")
265    @generated_natural_language_supported.setter
266    def generated_natural_language_supported(self, val):
267        raise ipp.errors.AttributesNotSettable("generated-natural-language-supported")
268
269    @property
270    def document_format_default(self):
271        return ipp.DocumentFormatDefault("application/octet-stream")
272    @document_format_default.setter
273    def document_format_default(self, val):
274        raise ipp.errors.AttributesNotSettable("document-format-default")
275
276    @property
277    def document_format_supported(self):
278        return ipp.DocumentFormatSupported("application/octet-stream", "audio/mp3")
279    @document_format_supported.setter
280    def document_format_supported(self, val):
281        raise ipp.errors.AttributesNotSettable("document-format-supported")
282
283    @property
284    def printer_is_accepting_jobs(self):
285        return ipp.PrinterIsAcceptingJobs(True)
286    @printer_is_accepting_jobs.setter
287    def printer_is_accepting_jobs(self, val):
288        raise ipp.errors.AttributesNotSettable("printer-is-accepting-jobs")
289
290    @property
291    def queued_job_count(self):
292        return ipp.QueuedJobCount(len(self.active_jobs))
293    @queued_job_count.setter
294    def queued_job_count(self, val):
295        raise ipp.errors.AttributesNotSettable("queued-job-count")
296
297    @property
298    def pdl_override_supported(self):
299        return ipp.PdlOverrideSupported("not-attempted")
300    @pdl_override_supported.setter
301    def pdl_override_supported(self, val):
302        raise ipp.errors.AttributesNotSettable("pdl-override-supported")
303
304    @property
305    def printer_up_time(self):
306        return ipp.PrinterUpTime(int(time.time()) - self.time_created)
307    @printer_up_time.setter
308    def printer_up_time(self, val):
309        raise ipp.errors.AttributesNotSettable("printer-up-time")
310
311    @property
312    def compression_supported(self):
313        return ipp.CompressionSupported("none")
314    @compression_supported.setter
315    def compression_supported(self, val):
316        raise ipp.errors.AttributesNotSettable("compression-supported")
317
318    @property
319    def multiple_operation_time_out(self):
320        return ipp.MultipleOperationTimeOut(240)
321    @multiple_operation_time_out.setter
322    def multiple_operation_time_out(self, val):
323        raise ipp.errors.AttributesNotSettable("multiple-operation-time-out")
324
325    @property
326    def multiple_document_jobs_supported(self):
327        return ipp.MultipleDocumentJobsSupported(False)
328    @multiple_document_jobs_supported.setter
329    def multiple_document_jobs_supported(self, val):
330        raise ipp.errors.AttributesNotSettable("multiple-document-jobs-supported")
331
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)
359
360    ######################################################################
361    ###                        IPP Operations                          ###
362    ######################################################################
363
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):
367
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
399
400    def get_jobs(self, requesting_user_name=None, which_jobs=None,
401                 requested_attributes=None):
402       
403        # Filter by the which-jobs attribute
404        if which_jobs is None:
405            which_jobs = "not-completed"
406
407        if which_jobs == "completed":
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]
411        else:
412            raise ipp.errors.ClientErrorAttributes(
413                which_jobs, ipp.WhichJobs(which_jobs))
414
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]
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]
424       
425        return job_attrs
426
427    def print_uri(self):
428        pass
429
430    def create_job(self, requesting_user_name=None, job_name=None,
431                   job_k_octets=None):
432
433        job_id = self._next_job_id
434        self._next_job_id += 1
435       
436        job = GutenbachJob(
437            job_id,
438            creator=requesting_user_name,
439            name=job_name)
440
441        self.jobs[job_id] = job
442        self.pending_jobs.append(job_id)
443       
444        return job_id
445
446    @sync
447    def pause_printer(self):
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
461    def resume_printer(self):
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
473
474    @sync
475    def get_printer_attributes(self, requested_attributes=None):
476        if requested_attributes is None:
477            requested = self.printer_attributes
478        else:
479            requested = [a for a in self.printer_attributes \
480                         if a in requested_attributes]
481
482        _attributes = [attr.replace("-", "_") for attr in requested]
483        attributes = [getattr(self, attr) for attr in _attributes]
484        return attributes
485
486    @sync
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
493
494    @sync
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
503    @sync
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)
510        job.spool(document)
511
512    @sync
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)
521
522    @sync
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
534    @sync
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?
544
545    @sync
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
553
554        self.finished_jobs.remove(job_id)
555        self.pending_jobs.append(job_id)
556
557    @sync
558    def promote_job(self, job_id, requesting_user_name=None):
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.