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
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
11
12
13# initialize logger
14logger = logging.getLogger(__name__)
15
16class GutenbachPrinter(threading.Thread):
17
18    # for IPP
19    printer_attributes = [
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",
38        "compression-supported",
39        "multiple-operation-time-out",
40        "multiple-document-jobs-supported",
41    ]
42
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
52    operations = [
53        "print-job",
54        "validate-job",
55        "get-jobs",
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"
69    ]
70       
71    def __init__(self, name, config, *args, **kwargs):
72
73        super(GutenbachPrinter, self).__init__(*args, **kwargs)
74       
75        self.name = name
76        self.config = config
77        self.time_created = int(time.time())
78
79        self.finished_jobs = []
80        self.pending_jobs = []
81        self.current_job = None
82        self.jobs = {}
83
84        self.lock = threading.RLock()
85        self.running = False
86        self.paused = False
87
88        # CUPS ignores jobs with id 0, so we have to start at 1
89        self._next_job_id = 1
90
91    def __repr__(self):
92        return str(self)
93
94    def __str__(self):
95        return "<Printer '%s'>" % self.name
96
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()
104                    elif self.current_job.is_done:
105                        self.complete_job()
106                except:
107                    logger.fatal(traceback.format_exc())
108                    sys.exit(1)
109            time.sleep(0.1)
110
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
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
143
144    ######################################################################
145    ###                            Methods                             ###
146    ######################################################################
147
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:
156                    self.current_job = None
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:
167                if not self.current_job.is_done:
168                    self.current_job.stop()
169            finally:
170                self.finished_jobs.append(self.current_job.id)
171                self.current_job = None
172
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
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):
365        pass
366
367    def validate_job(self):
368        pass
369
370    def get_jobs(self, requesting_user_name=None, which_jobs=None,
371                 requested_attributes=None):
372       
373        # Filter by the which-jobs attribute
374        if which_jobs is None:
375            which_jobs = "not-completed"
376
377        if which_jobs == "completed":
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]
381        else:
382            raise ipp.errors.ClientErrorAttributes(
383                which_jobs, ipp.WhichJobs(which_jobs))
384
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]
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]
394       
395        return job_attrs
396
397    def print_uri(self):
398        pass
399
400    def create_job(self, requesting_user_name=None, job_name=None, job_k_octets=None):
401        job_id = self._next_job_id
402        self._next_job_id += 1
403       
404        job = GutenbachJob(
405            job_id,
406            creator=requesting_user_name,
407            name=job_name)
408       
409        self.jobs[job_id] = job
410        self.pending_jobs.append(job_id)
411       
412        return job_id
413
414    def pause_printer(self):
415        pass
416
417    def resume_printer(self):
418        pass
419
420    def get_printer_attributes(self, requested_attributes=None):
421        if requested_attributes is None:
422            requested = self.printer_attributes
423        else:
424            requested = [a for a in self.printer_attributes \
425                         if a in requested_attributes]
426
427        _attributes = [attr.replace("-", "_") for attr in requested]
428        attributes = [getattr(self, attr) for attr in _attributes]
429        return attributes
430
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
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)
452        job.spool(document)
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
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               
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)
489
490    def promote_job(self, job_id, requesting_user_name=None):
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.