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
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
187    @property
188    def uri_authentication_supported(self):
189        return ipp.UriAuthenticationSupported("none")
190
191    @property
192    def uri_security_supported(self):
193        return ipp.UriSecuritySupported("none")
194
195    @property
196    def printer_name(self):
197        return ipp.PrinterName(self.name)
198
199    @property
200    def printer_state(self):
201        return ipp.PrinterState(self.state)
202
203    @property
204    def printer_state_reasons(self):
205        return ipp.PrinterStateReasons("none")
206
207    @property
208    def ipp_versions_supported(self):
209        return ipp.IppVersionsSupported(*self.config['ipp-versions'])
210
211    # XXX: We should query ourself for the supported operations
212    @property
213    def operations_supported(self):
214        return ipp.OperationsSupported(ipp.OperationCodes.GET_JOBS)
215
216    @property
217    def charset_configured(self):
218        return ipp.CharsetConfigured("utf-8")
219
220    @property
221    def charset_supported(self):
222        return ipp.CharsetSupported("utf-8")
223
224    @property
225    def natural_language_configured(self):
226        return ipp.NaturalLanguageConfigured("en-us")
227
228    @property
229    def generated_natural_language_supported(self):
230        return ipp.GeneratedNaturalLanguageSupported("en-us")
231
232    @property
233    def document_format_default(self):
234        return ipp.DocumentFormatDefault("application/octet-stream")
235
236    @property
237    def document_format_supported(self):
238        return ipp.DocumentFormatSupported("application/octet-stream", "audio/mp3")
239
240    @property
241    def printer_is_accepting_jobs(self):
242        return ipp.PrinterIsAcceptingJobs(True)
243
244    @property
245    def queued_job_count(self):
246        return ipp.QueuedJobCount(len(self.active_jobs))
247
248    @property
249    def pdl_override_supported(self):
250        return ipp.PdlOverrideSupported("not-attempted")
251
252    @property
253    def printer_up_time(self):
254        return ipp.PrinterUpTime(int(time.time()) - self.time_created)
255
256    @property
257    def compression_supported(self):
258        return ipp.CompressionSupported("none")
259
260    @property
261    def multiple_operation_time_out(self):
262        return ipp.MultipleOperationTimeOut(240)
263
264    @property
265    def multiple_document_jobs_supported(self):
266        return ipp.MultipleDocumentJobsSupported(False)
267
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)
295
296    ######################################################################
297    ###                        IPP Operations                          ###
298    ######################################################################
299
300    def print_job(self):
301        pass
302
303    def validate_job(self):
304        pass
305
306    def get_jobs(self, requesting_user_name=None, which_jobs=None,
307                 requested_attributes=None):
308       
309        # Filter by the which-jobs attribute
310        if which_jobs is None:
311            which_jobs = "not-completed"
312
313        if which_jobs == "completed":
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]
317        else:
318            raise ipp.errors.ClientErrorAttributes(
319                which_jobs, ipp.WhichJobs(which_jobs))
320
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]
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]
330       
331        return job_attrs
332
333    def print_uri(self):
334        pass
335
336    def create_job(self, requesting_user_name=None, job_name=None, job_k_octets=None):
337        job_id = self._next_job_id
338        self._next_job_id += 1
339       
340        job = GutenbachJob(
341            job_id,
342            creator=requesting_user_name,
343            name=job_name)
344       
345        self.jobs[job_id] = job
346        self.pending_jobs.append(job_id)
347       
348        return job_id
349
350    @sync
351    def pause_printer(self):
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
365
366    @sync
367    def resume_printer(self):
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
379
380    def get_printer_attributes(self, requested_attributes=None):
381        if requested_attributes is None:
382            requested = self.printer_attributes
383        else:
384            requested = [a for a in self.printer_attributes \
385                         if a in requested_attributes]
386
387        _attributes = [attr.replace("-", "_") for attr in requested]
388        attributes = [getattr(self, attr) for attr in _attributes]
389        return attributes
390
391    def set_printer_attributes(self):
392        pass
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)
408        job.spool(document)
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.