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

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

Add restart job support to printer.py and job.py

  • Property mode set to 100644
File size: 12.5 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
188    @property
189    def uri_authentication_supported(self):
190        return ipp.UriAuthenticationSupported("none")
191
192    @property
193    def uri_security_supported(self):
194        return ipp.UriSecuritySupported("none")
195
196    @property
197    def printer_name(self):
198        return ipp.PrinterName(self.name)
199
200    @property
201    def printer_state(self):
202        return ipp.PrinterState(self.state)
203
204    @property
205    def printer_state_reasons(self):
206        return ipp.PrinterStateReasons("none")
207
208    @property
209    def ipp_versions_supported(self):
210        return ipp.IppVersionsSupported(*self.config['ipp-versions'])
211
212    # XXX: We should query ourself for the supported operations
213    @property
214    def operations_supported(self):
215        return ipp.OperationsSupported(ipp.OperationCodes.GET_JOBS)
216
217    @property
218    def charset_configured(self):
219        return ipp.CharsetConfigured("utf-8")
220
221    @property
222    def charset_supported(self):
223        return ipp.CharsetSupported("utf-8")
224
225    @property
226    def natural_language_configured(self):
227        return ipp.NaturalLanguageConfigured("en-us")
228
229    @property
230    def generated_natural_language_supported(self):
231        return ipp.GeneratedNaturalLanguageSupported("en-us")
232
233    @property
234    def document_format_default(self):
235        return ipp.DocumentFormatDefault("application/octet-stream")
236
237    @property
238    def document_format_supported(self):
239        return ipp.DocumentFormatSupported("application/octet-stream", "audio/mp3")
240
241    @property
242    def printer_is_accepting_jobs(self):
243        return ipp.PrinterIsAcceptingJobs(True)
244
245    @property
246    def queued_job_count(self):
247        return ipp.QueuedJobCount(len(self.active_jobs))
248
249    @property
250    def pdl_override_supported(self):
251        return ipp.PdlOverrideSupported("not-attempted")
252
253    @property
254    def printer_up_time(self):
255        return ipp.PrinterUpTime(int(time.time()) - self.time_created)
256
257    @property
258    def compression_supported(self):
259        return ipp.CompressionSupported("none")
260
261    @property
262    def multiple_operation_time_out(self):
263        return ipp.MultipleOperationTimeOut(240)
264
265    @property
266    def multiple_document_jobs_supported(self):
267        return ipp.MultipleDocumentJobsSupported(False)
268
269    ######################################################################
270    ###                      Job IPP Attributes                        ###
271    ######################################################################
272
273    def job_id(self, job_id):
274        job = self.get_job(job_id)
275        return ipp.JobId(job.id)
276
277    def job_name(self, job_id):
278        job = self.get_job(job_id)
279        return ipp.JobName(job.name)
280
281    def job_originating_user_name(self, job_id):
282        job = self.get_job(job_id)
283        return ipp.JobOriginatingUserName(job.creator)
284
285    def job_k_octets(self, job_id):
286        job = self.get_job(job_id)
287        return ipp.JobKOctets(job.size)
288
289    def job_state(self, job_id):
290        job = self.get_job(job_id)
291        return ipp.JobState(job.state)
292
293    def job_printer_uri(self, job_id):
294        job = self.get_job(job_id)
295        return ipp.JobPrinterUri(self.uri)
296
297    ######################################################################
298    ###                        IPP Operations                          ###
299    ######################################################################
300
301    def print_job(self):
302        pass
303
304    def validate_job(self):
305        pass
306
307    def get_jobs(self, requesting_user_name=None, which_jobs=None,
308                 requested_attributes=None):
309       
310        # Filter by the which-jobs attribute
311        if which_jobs is None:
312            which_jobs = "not-completed"
313
314        if which_jobs == "completed":
315            jobs = [self.jobs[job_id] for job_id in self.finished_jobs]
316        elif which_jobs == "not-completed":
317            jobs = [self.jobs[job_id] for job_id in self.active_jobs]
318        else:
319            raise ipp.errors.ClientErrorAttributes(
320                which_jobs, ipp.WhichJobs(which_jobs))
321
322        # Filter by username
323        if requesting_user_name is None:
324            user_jobs = jobs
325        else:
326            user_jobs = [job for job in jobs if job.creator == requesting_user_name]
327
328        # Get the attributes of each job
329        job_attrs = [self.get_job_attributes(
330            job.id, requested_attributes=requested_attributes) for job in user_jobs]
331       
332        return job_attrs
333
334    def print_uri(self):
335        pass
336
337    def create_job(self, requesting_user_name=None, job_name=None, job_k_octets=None):
338        job_id = self._next_job_id
339        self._next_job_id += 1
340       
341        job = GutenbachJob(
342            job_id,
343            creator=requesting_user_name,
344            name=job_name)
345       
346        self.jobs[job_id] = job
347        self.pending_jobs.append(job_id)
348       
349        return job_id
350
351    def pause_printer(self):
352        pass
353
354    def resume_printer(self):
355        pass
356
357    def get_printer_attributes(self, requested_attributes=None):
358        if requested_attributes is None:
359            requested = self.printer_attributes
360        else:
361            requested = [a for a in self.printer_attributes \
362                         if a in requested_attributes]
363
364        _attributes = [attr.replace("-", "_") for attr in requested]
365        attributes = [getattr(self, attr) for attr in _attributes]
366        return attributes
367
368    def set_printer_attributes(self):
369        pass
370
371    def cancel_job(self, job_id, requesting_user_name=None):
372        job = self.get_job(job_id)
373        try:
374            job.cancel()
375        except InvalidJobStateException:
376            # XXX
377            raise
378
379    def send_document(self, job_id, document, document_name=None,
380                      document_format=None, document_natural_language=None,
381                      requesting_user_name=None, compression=None,
382                      last_document=None):
383
384        job = self.get_job(job_id)
385        job.spool(document)
386
387    def send_uri(self):
388        pass
389
390    def get_job_attributes(self, job_id, requested_attributes=None):
391        if requested_attributes is None:
392            requested = self.job_attributes
393        else:
394            requested = [a for a in self.job_attributes \
395                         if a in requested_attributes]
396
397        _attributes = [attr.replace("-", "_") for attr in requested]
398        attributes = [getattr(self, attr)(job_id) for attr in _attributes]
399        return attributes
400
401    def set_job_attributes(self):
402        pass
403
404    def restart_job(self, job_id, requesting_user_name=None):
405        job = self.get_job(job_id)
406        try:
407            job.restart()
408        except InvalidJobStateException:
409            # XXX
410            raise ipp.errors.ClientErrorNotPossible
411
412        with self.lock:
413            self.finished_jobs.remove(job_id)
414            self.pending_jobs.append(job_id)
415
416    def promote_job(self, job_id, requesting_user_name=None):
417        # According to RFC 3998, we need to put the job at the front
418        # of the queue (so that when the currently playing job
419        # completes, this one will go next
420       
421        job = self.get_job(job_id)
422        job.priority = 1 # XXX we need to actually do something
423                         # correct here
Note: See TracBrowser for help on using the repository browser.