source: server/lib/gutenbach/server/printer.py @ 9da7428

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

Add support for set-job-attributes and set-printer-attributes in printer.py

  • Property mode set to 100644
File size: 16.3 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
11
[d04a689]12
13# initialize logger
14logger = logging.getLogger(__name__)
[776a659]15
[eee389a]16class GutenbachPrinter(threading.Thread):
[b2e077a]17
[1a63bf7]18    # for IPP
[33ea505]19    printer_attributes = [
[b2e077a]20        "printer-uri-supported",
21        "uri-authentication-supported",
22        "uri-security-supported",
23        "printer-name",
24        "printer-state",
25        "printer-state-reasons",
26        "ipp-versions-supported",
27        "operations-supported",
28        "charset-configured",
29        "charset-supported",
30        "natural-language-configured",
31        "generated-natural-language-supported",
32        "document-format-default",
33        "document-format-supported",
34        "printer-is-accepting-jobs",
35        "queued-job-count",
36        "pdl-override-supported",
37        "printer-up-time",
[f6e2532]38        "compression-supported",
39        "multiple-operation-time-out",
40        "multiple-document-jobs-supported",
[1a63bf7]41    ]
[b2e077a]42
[33ea505]43    job_attributes = [
44        "job-id",
45        "job-name",
46        "job-originating-user-name",
47        "job-k-octets",
48        "job-state",
49        "job-printer-uri"
50    ]
51
[f6e2532]52    operations = [
53        "print-job",
[33ea505]54        "validate-job",
[f6e2532]55        "get-jobs",
[33ea505]56        "print-uri",
57        "create-job",
58        "pause-printer",
59        "resume-printer",
60        "get-printer-attributes",
61        "set-printer-attributes",
62        "cancel-job",
63        "send-document",
64        "send-uri",
65        "get-job-attributes",
66        "set-job-attributes",
67        "restart-job",
68        "promote-job"
[f6e2532]69    ]
70       
[609a9b0]71    def __init__(self, name, config, *args, **kwargs):
[eee389a]72
[d21198f]73        super(GutenbachPrinter, self).__init__(*args, **kwargs)
74       
75        self.name = name
[609a9b0]76        self.config = config
[d21198f]77        self.time_created = int(time.time())
[b2e077a]78
[d21198f]79        self.finished_jobs = []
80        self.pending_jobs = []
81        self.current_job = None
82        self.jobs = {}
[b2e077a]83
[d21198f]84        self.lock = threading.RLock()
85        self.running = False
86        self.paused = False
[776a659]87
[d21198f]88        # CUPS ignores jobs with id 0, so we have to start at 1
89        self._next_job_id = 1
[b01b6d1]90
91    def __repr__(self):
92        return str(self)
93
94    def __str__(self):
95        return "<Printer '%s'>" % self.name
96
[33ea505]97    def run(self):
98        self.running = True
99        while self.running:
100            with self.lock:
101                try:
102                    if self.current_job is None:
103                        self.start_job()
[345c476]104                    elif self.current_job.is_done:
[33ea505]105                        self.complete_job()
106                except:
107                    logger.fatal(traceback.format_exc())
108                    sys.exit(1)
109            time.sleep(0.1)
110
[b01b6d1]111    ######################################################################
112    ###                          Properties                            ###
113    ######################################################################
114
115    @property
116    def uris(self):
117        uris = ["ipp://localhost:8000/printers/" + self.name,
118                "ipp://localhost/printers/" + self.name]
119        return uris
120   
121    @property
122    def uri(self):
123        return self.uris[0]
124
125    @property
[eee389a]126    def state(self):
127        with self.lock:
128            if self.current_job is not None:
129                val = States.PROCESSING
130            elif len(self.pending_jobs) == 0:
131                val = States.IDLE
132            else:
133                val = States.STOPPED
134        return val
135
136    @property
137    def active_jobs(self):
138        with self.lock:
139            jobs = self.pending_jobs[:]
140            if self.current_job is not None:
141                jobs.insert(0, self.current_job.id)
142        return jobs
[b01b6d1]143
144    ######################################################################
145    ###                            Methods                             ###
146    ######################################################################
147
[eee389a]148    def start_job(self):
149        with self.lock:
150            if self.current_job is None:
151                try:
152                    job_id = heapq.heappop(self.pending_jobs)
153                    self.current_job = self.get_job(job_id)
154                    self.current_job.play()
155                except IndexError:
[d21198f]156                    self.current_job = None
[eee389a]157                except InvalidJobStateException:
158                    heapq.heappush(self.pending_jobs, self.current_job.id)
159                    self.current_job = None
160                   
161    def complete_job(self):
162        with self.lock:
163            if self.current_job is None:
164                return
165
166            try:
[345c476]167                if not self.current_job.is_done:
[eee389a]168                    self.current_job.stop()
169            finally:
[190bfb4]170                self.finished_jobs.append(self.current_job.id)
[eee389a]171                self.current_job = None
[1a63bf7]172
[eee389a]173    def get_job(self, job_id):
174        with self.lock:
175            if job_id not in self.jobs:
176                raise InvalidJobException(job_id)
177            job = self.jobs[job_id]
178        return job
[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
364    def print_job(self):
365        pass
366
367    def validate_job(self):
368        pass
369
[33ea505]370    def get_jobs(self, requesting_user_name=None, which_jobs=None,
371                 requested_attributes=None):
372       
[b01b6d1]373        # Filter by the which-jobs attribute
374        if which_jobs is None:
[34a4e5d]375            which_jobs = "not-completed"
376
377        if which_jobs == "completed":
[b01b6d1]378            jobs = [self.jobs[job_id] for job_id in self.finished_jobs]
379        elif which_jobs == "not-completed":
380            jobs = [self.jobs[job_id] for job_id in self.active_jobs]
[ee8e6d0]381        else:
[b01b6d1]382            raise ipp.errors.ClientErrorAttributes(
383                which_jobs, ipp.WhichJobs(which_jobs))
[b2e077a]384
[b01b6d1]385        # Filter by username
386        if requesting_user_name is None:
387            user_jobs = jobs
388        else:
389            user_jobs = [job for job in jobs if job.creator == requesting_user_name]
[33ea505]390
391        # Get the attributes of each job
392        job_attrs = [self.get_job_attributes(
393            job.id, requested_attributes=requested_attributes) for job in user_jobs]
[ee8e6d0]394       
[33ea505]395        return job_attrs
[ee8e6d0]396
[b01b6d1]397    def print_uri(self):
398        pass
399
[190bfb4]400    def create_job(self, requesting_user_name=None, job_name=None, job_k_octets=None):
[eee389a]401        job_id = self._next_job_id
402        self._next_job_id += 1
[ee8e6d0]403       
[33ea505]404        job = GutenbachJob(
405            job_id,
406            creator=requesting_user_name,
407            name=job_name)
[b01b6d1]408       
[ee8e6d0]409        self.jobs[job_id] = job
[eee389a]410        self.pending_jobs.append(job_id)
[b01b6d1]411       
[33ea505]412        return job_id
[776a659]413
[b01b6d1]414    def pause_printer(self):
[ee8e6d0]415        pass
[776a659]416
[b01b6d1]417    def resume_printer(self):
418        pass
[776a659]419
[b01b6d1]420    def get_printer_attributes(self, requested_attributes=None):
421        if requested_attributes is None:
[33ea505]422            requested = self.printer_attributes
[e58af05]423        else:
[33ea505]424            requested = [a for a in self.printer_attributes \
425                         if a in requested_attributes]
[b2e077a]426
[b01b6d1]427        _attributes = [attr.replace("-", "_") for attr in requested]
428        attributes = [getattr(self, attr) for attr in _attributes]
429        return attributes
[776a659]430
[9da7428]431    def set_printer_attributes(self, job_id, attributes):
432        for attr in attributes:
433            try:
434                setattr(self, attr, attributes[attr])
435            except AttributeError:
436                raise ipp.errors.ClientErrorAttributes
[33ea505]437
438    def cancel_job(self, job_id, requesting_user_name=None):
439        job = self.get_job(job_id)
440        try:
441            job.cancel()
442        except InvalidJobStateException:
443            # XXX
444            raise
445
446    def send_document(self, job_id, document, document_name=None,
447                      document_format=None, document_natural_language=None,
448                      requesting_user_name=None, compression=None,
449                      last_document=None):
450
451        job = self.get_job(job_id)
[345c476]452        job.spool(document)
[33ea505]453
454    def send_uri(self):
455        pass
456
457    def get_job_attributes(self, job_id, requested_attributes=None):
458        if requested_attributes is None:
459            requested = self.job_attributes
460        else:
461            requested = [a for a in self.job_attributes \
462                         if a in requested_attributes]
463
464        _attributes = [attr.replace("-", "_") for attr in requested]
465        attributes = [getattr(self, attr)(job_id) for attr in _attributes]
466        return attributes
467
[9da7428]468    def set_job_attributes(self, job_id, attributes):
469        job = self.get_job(job_id)
470        for attr in attributes:
471            if attr in ("job-id", "job-k-octets", "job-state", "job-printer-uri"):
472                raise ipp.errors.ClientErrorAttributesNotSettable(attr)
473            elif attr == "job-name":
474                job.name = attributes[attr]
475            elif attr == "job-originating-user-name":
476                job.creator = attributes[attr] # XXX: do we want this?
477               
[c1cebbc]478    def restart_job(self, job_id, requesting_user_name=None):
479        job = self.get_job(job_id)
480        try:
481            job.restart()
482        except InvalidJobStateException:
483            # XXX
484            raise ipp.errors.ClientErrorNotPossible
485
486        with self.lock:
487            self.finished_jobs.remove(job_id)
488            self.pending_jobs.append(job_id)
[33ea505]489
[c1cebbc]490    def promote_job(self, job_id, requesting_user_name=None):
[c500bc2]491        # According to RFC 3998, we need to put the job at the front
492        # of the queue (so that when the currently playing job
493        # completes, this one will go next
494       
495        job = self.get_job(job_id)
496        job.priority = 1 # XXX we need to actually do something
497                         # correct here
Note: See TracBrowser for help on using the repository browser.