source: server/lib/gutenbach/server/printer.py @ c1dc25f

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

Send URI added to printer.py

  • Property mode set to 100644
File size: 16.6 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, job_id, document_uri, document_name=None,
455                 document_format=None, document_natural_language=None,
456                 requesting_user_name=None, compression=None,
457                 last_document=None):
458        job = self.get_job(job_id)
459        # XXX: need to validate URI
460        # XXX: need to deal with the URI stream?
461        #job.spool_uri(document_uri)
462
463    def get_job_attributes(self, job_id, requested_attributes=None):
464        if requested_attributes is None:
465            requested = self.job_attributes
466        else:
467            requested = [a for a in self.job_attributes \
468                         if a in requested_attributes]
469
470        _attributes = [attr.replace("-", "_") for attr in requested]
471        attributes = [getattr(self, attr)(job_id) for attr in _attributes]
472        return attributes
473
474    def set_job_attributes(self, job_id, attributes):
475        job = self.get_job(job_id)
476        for attr in attributes:
477            if attr in ("job-id", "job-k-octets", "job-state", "job-printer-uri"):
478                raise ipp.errors.ClientErrorAttributesNotSettable(attr)
479            elif attr == "job-name":
480                job.name = attributes[attr]
481            elif attr == "job-originating-user-name":
482                job.creator = attributes[attr] # XXX: do we want this?
483               
484    def restart_job(self, job_id, requesting_user_name=None):
485        job = self.get_job(job_id)
486        try:
487            job.restart()
488        except InvalidJobStateException:
489            # XXX
490            raise ipp.errors.ClientErrorNotPossible
491
492        with self.lock:
493            self.finished_jobs.remove(job_id)
494            self.pending_jobs.append(job_id)
495
496    def promote_job(self, job_id, requesting_user_name=None):
497        # According to RFC 3998, we need to put the job at the front
498        # of the queue (so that when the currently playing job
499        # completes, this one will go next
500       
501        job = self.get_job(job_id)
502        job.priority = 1 # XXX we need to actually do something
503                         # correct here
Note: See TracBrowser for help on using the repository browser.