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

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

Add pausing and resuming to the printer.

  • Property mode set to 100644
File size: 12.2 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
[cf0d7e8]11from . import sync
[eee389a]12
[d04a689]13
14# initialize logger
15logger = logging.getLogger(__name__)
[776a659]16
[eee389a]17class GutenbachPrinter(threading.Thread):
[b2e077a]18
[1a63bf7]19    # for IPP
[33ea505]20    printer_attributes = [
[b2e077a]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",
[f6e2532]39        "compression-supported",
40        "multiple-operation-time-out",
41        "multiple-document-jobs-supported",
[1a63bf7]42    ]
[b2e077a]43
[33ea505]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
[f6e2532]53    operations = [
54        "print-job",
[33ea505]55        "validate-job",
[f6e2532]56        "get-jobs",
[33ea505]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"
[f6e2532]70    ]
71       
[609a9b0]72    def __init__(self, name, config, *args, **kwargs):
[eee389a]73
[d21198f]74        super(GutenbachPrinter, self).__init__(*args, **kwargs)
75       
76        self.name = name
[609a9b0]77        self.config = config
[d21198f]78        self.time_created = int(time.time())
[b2e077a]79
[d21198f]80        self.finished_jobs = []
81        self.pending_jobs = []
82        self.current_job = None
83        self.jobs = {}
[b2e077a]84
[d21198f]85        self.lock = threading.RLock()
86        self.running = False
87        self.paused = False
[776a659]88
[d21198f]89        # CUPS ignores jobs with id 0, so we have to start at 1
90        self._next_job_id = 1
[b01b6d1]91
92    def __repr__(self):
93        return str(self)
94
95    def __str__(self):
96        return "<Printer '%s'>" % self.name
97
[33ea505]98    def run(self):
99        self.running = True
100        while self.running:
101            with self.lock:
102                try:
[fa3e2c6]103                    if not self.paused and self.current_job is None:
[33ea505]104                        self.start_job()
[345c476]105                    elif self.current_job.is_done:
[33ea505]106                        self.complete_job()
107                except:
108                    logger.fatal(traceback.format_exc())
109                    sys.exit(1)
110            time.sleep(0.1)
111
[b01b6d1]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
[cf0d7e8]127    @sync
[eee389a]128    def state(self):
[cf0d7e8]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
[eee389a]135
136    @property
[cf0d7e8]137    @sync
[eee389a]138    def active_jobs(self):
[cf0d7e8]139        jobs = self.pending_jobs[:]
140        if self.current_job is not None:
141            jobs.insert(0, self.current_job.id)
[eee389a]142        return jobs
[b01b6d1]143
144    ######################################################################
145    ###                            Methods                             ###
146    ######################################################################
147
[cf0d7e8]148    @sync
[eee389a]149    def start_job(self):
[cf0d7e8]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
[eee389a]160                   
[cf0d7e8]161    @sync
[eee389a]162    def complete_job(self):
[cf0d7e8]163        if self.current_job is None:
164            return
[eee389a]165
[cf0d7e8]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
[1a63bf7]172
[cf0d7e8]173    @sync
[eee389a]174    def get_job(self, job_id):
[cf0d7e8]175        if job_id not in self.jobs:
176            raise InvalidJobException(job_id)
177        return self.jobs[job_id]
[b01b6d1]178
179    ######################################################################
180    ###                        IPP Attributes                          ###
181    ######################################################################
[1a63bf7]182
[b2e077a]183    @property
184    def printer_uri_supported(self):
[793432f]185        return ipp.PrinterUriSupported(self.uri)
[1a63bf7]186
[b2e077a]187    @property
188    def uri_authentication_supported(self):
[793432f]189        return ipp.UriAuthenticationSupported("none")
[b2e077a]190
191    @property
192    def uri_security_supported(self):
[793432f]193        return ipp.UriSecuritySupported("none")
[b2e077a]194
195    @property
196    def printer_name(self):
[793432f]197        return ipp.PrinterName(self.name)
[1a63bf7]198
[b2e077a]199    @property
200    def printer_state(self):
[b01b6d1]201        return ipp.PrinterState(self.state)
[1a63bf7]202
[b2e077a]203    @property
204    def printer_state_reasons(self):
[793432f]205        return ipp.PrinterStateReasons("none")
[b2e077a]206
207    @property
208    def ipp_versions_supported(self):
[609a9b0]209        return ipp.IppVersionsSupported(*self.config['ipp-versions'])
[1a63bf7]210
[f6e2532]211    # XXX: We should query ourself for the supported operations
[b2e077a]212    @property
213    def operations_supported(self):
[793432f]214        return ipp.OperationsSupported(ipp.OperationCodes.GET_JOBS)
[b2e077a]215
216    @property
217    def charset_configured(self):
[793432f]218        return ipp.CharsetConfigured("utf-8")
[b2e077a]219
220    @property
221    def charset_supported(self):
[793432f]222        return ipp.CharsetSupported("utf-8")
[b2e077a]223
224    @property
225    def natural_language_configured(self):
[793432f]226        return ipp.NaturalLanguageConfigured("en-us")
[b2e077a]227
228    @property
229    def generated_natural_language_supported(self):
[793432f]230        return ipp.GeneratedNaturalLanguageSupported("en-us")
[b2e077a]231
232    @property
233    def document_format_default(self):
[793432f]234        return ipp.DocumentFormatDefault("application/octet-stream")
[b2e077a]235
236    @property
237    def document_format_supported(self):
[793432f]238        return ipp.DocumentFormatSupported("application/octet-stream", "audio/mp3")
[b2e077a]239
240    @property
241    def printer_is_accepting_jobs(self):
[793432f]242        return ipp.PrinterIsAcceptingJobs(True)
[b2e077a]243
244    @property
245    def queued_job_count(self):
[793432f]246        return ipp.QueuedJobCount(len(self.active_jobs))
[b2e077a]247
248    @property
249    def pdl_override_supported(self):
[793432f]250        return ipp.PdlOverrideSupported("not-attempted")
[b2e077a]251
252    @property
253    def printer_up_time(self):
[793432f]254        return ipp.PrinterUpTime(int(time.time()) - self.time_created)
[b2e077a]255
256    @property
257    def compression_supported(self):
[793432f]258        return ipp.CompressionSupported("none")
[b2e077a]259
[f6e2532]260    @property
261    def multiple_operation_time_out(self):
[793432f]262        return ipp.MultipleOperationTimeOut(240)
[f6e2532]263
264    @property
265    def multiple_document_jobs_supported(self):
[793432f]266        return ipp.MultipleDocumentJobsSupported(False)
[f6e2532]267
[33ea505]268    ######################################################################
269    ###                      Job IPP Attributes                        ###
270    ######################################################################
271
272    def job_id(self, job_id):
273        job = self.get_job(job_id)
274        return ipp.JobId(job.id)
275
276    def job_name(self, job_id):
277        job = self.get_job(job_id)
278        return ipp.JobName(job.name)
279
280    def job_originating_user_name(self, job_id):
281        job = self.get_job(job_id)
282        return ipp.JobOriginatingUserName(job.creator)
283
284    def job_k_octets(self, job_id):
285        job = self.get_job(job_id)
286        return ipp.JobKOctets(job.size)
287
288    def job_state(self, job_id):
289        job = self.get_job(job_id)
290        return ipp.JobState(job.state)
291
292    def job_printer_uri(self, job_id):
293        job = self.get_job(job_id)
294        return ipp.JobPrinterUri(self.uri)
[ee8e6d0]295
[b01b6d1]296    ######################################################################
297    ###                        IPP Operations                          ###
298    ######################################################################
299
300    def print_job(self):
301        pass
302
303    def validate_job(self):
304        pass
305
[33ea505]306    def get_jobs(self, requesting_user_name=None, which_jobs=None,
307                 requested_attributes=None):
308       
[b01b6d1]309        # Filter by the which-jobs attribute
310        if which_jobs is None:
[34a4e5d]311            which_jobs = "not-completed"
312
313        if which_jobs == "completed":
[b01b6d1]314            jobs = [self.jobs[job_id] for job_id in self.finished_jobs]
315        elif which_jobs == "not-completed":
316            jobs = [self.jobs[job_id] for job_id in self.active_jobs]
[ee8e6d0]317        else:
[b01b6d1]318            raise ipp.errors.ClientErrorAttributes(
319                which_jobs, ipp.WhichJobs(which_jobs))
[b2e077a]320
[b01b6d1]321        # Filter by username
322        if requesting_user_name is None:
323            user_jobs = jobs
324        else:
325            user_jobs = [job for job in jobs if job.creator == requesting_user_name]
[33ea505]326
327        # Get the attributes of each job
328        job_attrs = [self.get_job_attributes(
329            job.id, requested_attributes=requested_attributes) for job in user_jobs]
[ee8e6d0]330       
[33ea505]331        return job_attrs
[ee8e6d0]332
[b01b6d1]333    def print_uri(self):
334        pass
335
[190bfb4]336    def create_job(self, requesting_user_name=None, job_name=None, job_k_octets=None):
[eee389a]337        job_id = self._next_job_id
338        self._next_job_id += 1
[ee8e6d0]339       
[33ea505]340        job = GutenbachJob(
341            job_id,
342            creator=requesting_user_name,
343            name=job_name)
[b01b6d1]344       
[ee8e6d0]345        self.jobs[job_id] = job
[eee389a]346        self.pending_jobs.append(job_id)
[b01b6d1]347       
[33ea505]348        return job_id
[776a659]349
[fa3e2c6]350    @sync
[b01b6d1]351    def pause_printer(self):
[fa3e2c6]352        """Pause the printer.
353
354        Does nothing if the printer is already paused.
355        """
356        if self.paused:
357            return
358
359        if self.current_job is not None and self.current_job.is_playing:
360            self.current_job.pause()
361
362        self.paused = True
363
364
[776a659]365
[fa3e2c6]366    @sync
[b01b6d1]367    def resume_printer(self):
[fa3e2c6]368        """Resume the printer.
369
370        Does nothing if the printer is not paused.
371        """
372        if not self.paused:
373            return
374
375        if self.current_job is not None:
376            self.current_job.resume()
377
378        self.paused = False
[776a659]379
[b01b6d1]380    def get_printer_attributes(self, requested_attributes=None):
381        if requested_attributes is None:
[33ea505]382            requested = self.printer_attributes
[e58af05]383        else:
[33ea505]384            requested = [a for a in self.printer_attributes \
385                         if a in requested_attributes]
[b2e077a]386
[b01b6d1]387        _attributes = [attr.replace("-", "_") for attr in requested]
388        attributes = [getattr(self, attr) for attr in _attributes]
389        return attributes
[776a659]390
[b01b6d1]391    def set_printer_attributes(self):
392        pass
[33ea505]393
394    def cancel_job(self, job_id, requesting_user_name=None):
395        job = self.get_job(job_id)
396        try:
397            job.cancel()
398        except InvalidJobStateException:
399            # XXX
400            raise
401
402    def send_document(self, job_id, document, document_name=None,
403                      document_format=None, document_natural_language=None,
404                      requesting_user_name=None, compression=None,
405                      last_document=None):
406
407        job = self.get_job(job_id)
[345c476]408        job.spool(document)
[33ea505]409
410    def send_uri(self):
411        pass
412
413    def get_job_attributes(self, job_id, requested_attributes=None):
414        if requested_attributes is None:
415            requested = self.job_attributes
416        else:
417            requested = [a for a in self.job_attributes \
418                         if a in requested_attributes]
419
420        _attributes = [attr.replace("-", "_") for attr in requested]
421        attributes = [getattr(self, attr)(job_id) for attr in _attributes]
422        return attributes
423
424    def set_job_attributes(self):
425        pass
426
427    def restart_job(self):
428        pass
429
430    def promote_job(self):
431        pass
Note: See TracBrowser for help on using the repository browser.