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

no-cups
Last change on this file since 9225351 was 9225351, checked in by Steven Allen <steven@…>, 12 years ago

Merge branch 'no-cups' of github.com:jhamrick/gutenbach into no-cups

  • Property mode set to 100644
File size: 16.9 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
11from . import sync
12
13
14# initialize logger
15logger = logging.getLogger(__name__)
16
17class GutenbachPrinter(threading.Thread):
18
19    # for IPP
20    printer_attributes = [
21        "printer-uri-supported",
22        "uri-authentication-supported",
23        "uri-security-supported",
24        "printer-name",
25        "printer-state",
26        "printer-state-reasons",
27        "ipp-versions-supported",
28        "operations-supported",
29        "charset-configured",
30        "charset-supported",
31        "natural-language-configured",
32        "generated-natural-language-supported",
33        "document-format-default",
34        "document-format-supported",
35        "printer-is-accepting-jobs",
36        "queued-job-count",
37        "pdl-override-supported",
38        "printer-up-time",
39        "compression-supported",
40        "multiple-operation-time-out",
41        "multiple-document-jobs-supported",
42    ]
43
44    job_attributes = [
45        "job-id",
46        "job-name",
47        "job-originating-user-name",
48        "job-k-octets",
49        "job-state",
50        "job-printer-uri"
51    ]
52
53    operations = [
54        "print-job",
55        "validate-job",
56        "get-jobs",
57        "print-uri",
58        "create-job",
59        "pause-printer",
60        "resume-printer",
61        "get-printer-attributes",
62        "set-printer-attributes",
63        "cancel-job",
64        "send-document",
65        "send-uri",
66        "get-job-attributes",
67        "set-job-attributes",
68        "restart-job",
69        "promote-job"
70    ]
71       
72    def __init__(self, name, config, *args, **kwargs):
73
74        super(GutenbachPrinter, self).__init__(*args, **kwargs)
75       
76        self.name = name
77        self.config = config
78        self.time_created = int(time.time())
79
80        self.finished_jobs = []
81        self.pending_jobs = []
82        self.current_job = None
83        self.jobs = {}
84
85        self.lock = threading.RLock()
86        self.running = False
87        self.paused = False
88
89        # CUPS ignores jobs with id 0, so we have to start at 1
90        self._next_job_id = 1
91
92    def __repr__(self):
93        return str(self)
94
95    def __str__(self):
96        return "<Printer '%s'>" % self.name
97
98    def run(self):
99        self.running = True
100        while self.running:
101            with self.lock:
102                try:
103                    if not self.paused and self.current_job is None:
104                        self.start_job()
105                    elif self.current_job.is_done:
106                        self.complete_job()
107                except:
108                    logger.fatal(traceback.format_exc())
109                    sys.exit(1)
110            time.sleep(0.1)
111
112    ######################################################################
113    ###                          Properties                            ###
114    ######################################################################
115
116    @property
117    def uris(self):
118        uris = ["ipp://localhost:8000/printers/" + self.name,
119                "ipp://localhost/printers/" + self.name]
120        return uris
121   
122    @property
123    def uri(self):
124        return self.uris[0]
125
126    @property
127    @sync
128    def state(self):
129        if self.current_job is not None:
130            return States.PROCESSING
131        elif len(self.pending_jobs) == 0:
132            return States.IDLE
133        else:
134            return States.STOPPED
135
136    @property
137    @sync
138    def active_jobs(self):
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    @sync
149    def start_job(self):
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    @sync
162    def complete_job(self):
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    @sync
174    def get_job(self, job_id):
175        if job_id not in self.jobs:
176            raise InvalidJobException(job_id)
177        return self.jobs[job_id]
178
179    ######################################################################
180    ###                        IPP Attributes                          ###
181    ######################################################################
182
183    @property
184    def printer_uri_supported(self):
185        return ipp.PrinterUriSupported(self.uri)
186    @printer_uri_supported.setter
187    def printer_uri_supported(self, val):
188        raise ipp.errors.AttributesNotSettable("printer-uri-supported")
189
190    @property
191    def uri_authentication_supported(self):
192        return ipp.UriAuthenticationSupported("none")
193    @uri_authentication_supported.setter
194    def uri_authentication_supported(self, val):
195        raise ipp.errors.AttributesNotSettable("uri-authentication-supported")
196
197    @property
198    def uri_security_supported(self):
199        return ipp.UriSecuritySupported("none")
200    @uri_security_supported.setter
201    def uri_security_supported(self, val):
202        raise ipp.errors.AttributesNotSettable("uri-security-supported")
203
204    @property
205    def printer_name(self):
206        return ipp.PrinterName(self.name)
207    @printer_name.setter
208    def printer_name(self, val):
209        raise ipp.errors.AttributesNotSettable("printer-name")
210
211    @property
212    def printer_state(self):
213        return ipp.PrinterState(self.state)
214    @printer_state.setter
215    def printer_state(self, val):
216        raise ipp.errors.AttributesNotSettable("printer-state")
217
218    @property
219    def printer_state_reasons(self):
220        return ipp.PrinterStateReasons("none")
221    @printer_state_reasons.setter
222    def printer_state_reasons(self, val):
223        raise ipp.errors.AttributesNotSettable("printer-state-reasons")
224
225    @property
226    def ipp_versions_supported(self):
227        return ipp.IppVersionsSupported(*self.config['ipp-versions'])
228    @ipp_versions_supported.setter
229    def ipp_versions_supported(self, val):
230        raise ipp.errors.AttributesNotSettable("ipp-versions-supported")
231
232    # XXX: We should query ourself for the supported operations
233    @property
234    def operations_supported(self):
235        return ipp.OperationsSupported(ipp.OperationCodes.GET_JOBS)
236    @operations_supported.setter
237    def operations_supported(self, val):
238        raise ipp.errors.AttributesNotSettable("operations-supported")
239
240    @property
241    def charset_configured(self):
242        return ipp.CharsetConfigured("utf-8") # XXX
243    @charset_configured.setter
244    def charset_configured(self, val):
245        raise ipp.errors.AttributesNotSettable("charset-configured")
246       
247    @property
248    def charset_supported(self):
249        return ipp.CharsetSupported("utf-8") # XXX
250    @charset_supported.setter
251    def charset_supported(self, val):
252        raise ipp.errors.AttributesNotSettable("charset-supported")
253
254    @property
255    def natural_language_configured(self):
256        return ipp.NaturalLanguageConfigured("en-us")
257    @natural_language_configured.setter
258    def natural_language_configured(self, val):
259        raise ipp.errors.AttributesNotSettable("natural-language-configured")
260
261    @property
262    def generated_natural_language_supported(self):
263        return ipp.GeneratedNaturalLanguageSupported("en-us")
264    @generated_natural_language_supported.setter
265    def generated_natural_language_supported(self, val):
266        raise ipp.errors.AttributesNotSettable("generated-natural-language-supported")
267
268    @property
269    def document_format_default(self):
270        return ipp.DocumentFormatDefault("application/octet-stream")
271    @document_format_default.setter
272    def document_format_default(self, val):
273        raise ipp.errors.AttributesNotSettable("document-format-default")
274
275    @property
276    def document_format_supported(self):
277        return ipp.DocumentFormatSupported("application/octet-stream", "audio/mp3")
278    @document_format_supported.setter
279    def document_format_supported(self, val):
280        raise ipp.errors.AttributesNotSettable("document-format-supported")
281
282    @property
283    def printer_is_accepting_jobs(self):
284        return ipp.PrinterIsAcceptingJobs(True)
285    @printer_is_accepting_jobs.setter
286    def printer_is_accepting_jobs(self, val):
287        raise ipp.errors.AttributesNotSettable("printer-is-accepting-jobs")
288
289    @property
290    def queued_job_count(self):
291        return ipp.QueuedJobCount(len(self.active_jobs))
292    @queued_job_count.setter
293    def queued_job_count(self, val):
294        raise ipp.errors.AttributesNotSettable("queued-job-count")
295
296    @property
297    def pdl_override_supported(self):
298        return ipp.PdlOverrideSupported("not-attempted")
299    @pdl_override_supported.setter
300    def pdl_override_supported(self, val):
301        raise ipp.errors.AttributesNotSettable("pdl-override-supported")
302
303    @property
304    def printer_up_time(self):
305        return ipp.PrinterUpTime(int(time.time()) - self.time_created)
306    @printer_up_time.setter
307    def printer_up_time(self, val):
308        raise ipp.errors.AttributesNotSettable("printer-up-time")
309
310    @property
311    def compression_supported(self):
312        return ipp.CompressionSupported("none")
313    @compression_supported.setter
314    def compression_supported(self, val):
315        raise ipp.errors.AttributesNotSettable("compression-supported")
316
317    @property
318    def multiple_operation_time_out(self):
319        return ipp.MultipleOperationTimeOut(240)
320    @multiple_operation_time_out.setter
321    def multiple_operation_time_out(self, val):
322        raise ipp.errors.AttributesNotSettable("multiple-operation-time-out")
323
324    @property
325    def multiple_document_jobs_supported(self):
326        return ipp.MultipleDocumentJobsSupported(False)
327    @multiple_document_jobs_supported.setter
328    def multiple_document_jobs_supported(self, val):
329        raise ipp.errors.AttributesNotSettable("multiple-document-jobs-supported")
330
331    ######################################################################
332    ###                      Job IPP Attributes                        ###
333    ######################################################################
334
335    def job_id(self, job_id):
336        job = self.get_job(job_id)
337        return ipp.JobId(job.id)
338
339    def job_name(self, job_id):
340        job = self.get_job(job_id)
341        return ipp.JobName(job.name)
342
343    def job_originating_user_name(self, job_id):
344        job = self.get_job(job_id)
345        return ipp.JobOriginatingUserName(job.creator)
346
347    def job_k_octets(self, job_id):
348        job = self.get_job(job_id)
349        return ipp.JobKOctets(job.size)
350
351    def job_state(self, job_id):
352        job = self.get_job(job_id)
353        return ipp.JobState(job.state)
354
355    def job_printer_uri(self, job_id):
356        job = self.get_job(job_id)
357        return ipp.JobPrinterUri(self.uri)
358
359    ######################################################################
360    ###                        IPP Operations                          ###
361    ######################################################################
362
363    def print_job(self):
364        pass
365
366    def validate_job(self):
367        pass
368
369    def get_jobs(self, requesting_user_name=None, which_jobs=None,
370                 requested_attributes=None):
371       
372        # Filter by the which-jobs attribute
373        if which_jobs is None:
374            which_jobs = "not-completed"
375
376        if which_jobs == "completed":
377            jobs = [self.jobs[job_id] for job_id in self.finished_jobs]
378        elif which_jobs == "not-completed":
379            jobs = [self.jobs[job_id] for job_id in self.active_jobs]
380        else:
381            raise ipp.errors.ClientErrorAttributes(
382                which_jobs, ipp.WhichJobs(which_jobs))
383
384        # Filter by username
385        if requesting_user_name is None:
386            user_jobs = jobs
387        else:
388            user_jobs = [job for job in jobs if job.creator == requesting_user_name]
389
390        # Get the attributes of each job
391        job_attrs = [self.get_job_attributes(
392            job.id, requested_attributes=requested_attributes) for job in user_jobs]
393       
394        return job_attrs
395
396    def print_uri(self):
397        pass
398
399    def create_job(self, requesting_user_name=None, job_name=None, job_k_octets=None):
400        job_id = self._next_job_id
401        self._next_job_id += 1
402       
403        job = GutenbachJob(
404            job_id,
405            creator=requesting_user_name,
406            name=job_name)
407       
408        self.jobs[job_id] = job
409        self.pending_jobs.append(job_id)
410       
411        return job_id
412
413    @sync
414    def pause_printer(self):
415        """Pause the printer.
416
417        Does nothing if the printer is already paused.
418        """
419        if self.paused:
420            return
421
422        if self.current_job is not None and self.current_job.is_playing:
423            self.current_job.pause()
424
425        self.paused = True
426
427
428
429    @sync
430    def resume_printer(self):
431        """Resume the printer.
432
433        Does nothing if the printer is not paused.
434        """
435        if not self.paused:
436            return
437
438        if self.current_job is not None:
439            self.current_job.resume()
440
441        self.paused = False
442
443    def get_printer_attributes(self, requested_attributes=None):
444        if requested_attributes is None:
445            requested = self.printer_attributes
446        else:
447            requested = [a for a in self.printer_attributes \
448                         if a in requested_attributes]
449
450        _attributes = [attr.replace("-", "_") for attr in requested]
451        attributes = [getattr(self, attr) for attr in _attributes]
452        return attributes
453
454    def set_printer_attributes(self, job_id, attributes):
455        for attr in attributes:
456            try:
457                setattr(self, attr, attributes[attr])
458            except AttributeError:
459                raise ipp.errors.ClientErrorAttributes
460
461    def cancel_job(self, job_id, requesting_user_name=None):
462        job = self.get_job(job_id)
463        try:
464            job.cancel()
465        except InvalidJobStateException:
466            # XXX
467            raise
468
469    def send_document(self, job_id, document, document_name=None,
470                      document_format=None, document_natural_language=None,
471                      requesting_user_name=None, compression=None,
472                      last_document=None):
473
474        job = self.get_job(job_id)
475        job.spool(document)
476
477    def send_uri(self, job_id, document_uri, document_name=None,
478                 document_format=None, document_natural_language=None,
479                 requesting_user_name=None, compression=None,
480                 last_document=None):
481        job = self.get_job(job_id)
482        # XXX: need to validate URI
483        # XXX: need to deal with the URI stream?
484        #job.spool_uri(document_uri)
485
486    def get_job_attributes(self, job_id, requested_attributes=None):
487        if requested_attributes is None:
488            requested = self.job_attributes
489        else:
490            requested = [a for a in self.job_attributes \
491                         if a in requested_attributes]
492
493        _attributes = [attr.replace("-", "_") for attr in requested]
494        attributes = [getattr(self, attr)(job_id) for attr in _attributes]
495        return attributes
496
497    def set_job_attributes(self, job_id, attributes):
498        job = self.get_job(job_id)
499        for attr in attributes:
500            if attr in ("job-id", "job-k-octets", "job-state", "job-printer-uri"):
501                raise ipp.errors.ClientErrorAttributesNotSettable(attr)
502            elif attr == "job-name":
503                job.name = attributes[attr]
504            elif attr == "job-originating-user-name":
505                job.creator = attributes[attr] # XXX: do we want this?
506               
507    def restart_job(self, job_id, requesting_user_name=None):
508        job = self.get_job(job_id)
509        try:
510            job.restart()
511        except InvalidJobStateException:
512            # XXX
513            raise ipp.errors.ClientErrorNotPossible
514
515        with self.lock:
516            self.finished_jobs.remove(job_id)
517            self.pending_jobs.append(job_id)
518
519    def promote_job(self, job_id, requesting_user_name=None):
520        # According to RFC 3998, we need to put the job at the front
521        # of the queue (so that when the currently playing job
522        # completes, this one will go next
523       
524        job = self.get_job(job_id)
525        job.priority = 1 # XXX we need to actually do something
526                         # correct here
Note: See TracBrowser for help on using the repository browser.