source: server/lib/gutenbach/server/printer.py @ 7e29e6a

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

Fix a few small bugs in printer.py

  • Property mode set to 100644
File size: 21.3 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
[57bc2dc]11import tempfile
[cf0d7e8]12from . import sync
[eee389a]13
[d04a689]14
15# initialize logger
16logger = logging.getLogger(__name__)
[776a659]17
[eee389a]18class GutenbachPrinter(threading.Thread):
[b2e077a]19
[1a63bf7]20    # for IPP
[33ea505]21    printer_attributes = [
[b2e077a]22        "printer-uri-supported",
23        "uri-authentication-supported",
24        "uri-security-supported",
25        "printer-name",
26        "printer-state",
27        "printer-state-reasons",
28        "ipp-versions-supported",
29        "operations-supported",
30        "charset-configured",
31        "charset-supported",
32        "natural-language-configured",
33        "generated-natural-language-supported",
34        "document-format-default",
35        "document-format-supported",
36        "printer-is-accepting-jobs",
37        "queued-job-count",
38        "pdl-override-supported",
39        "printer-up-time",
[f6e2532]40        "compression-supported",
41        "multiple-operation-time-out",
42        "multiple-document-jobs-supported",
[1a63bf7]43    ]
[b2e077a]44
[33ea505]45    job_attributes = [
46        "job-id",
47        "job-name",
48        "job-originating-user-name",
49        "job-k-octets",
50        "job-state",
51        "job-printer-uri"
52    ]
53
[f6e2532]54    operations = [
55        "print-job",
[33ea505]56        "validate-job",
[f6e2532]57        "get-jobs",
[33ea505]58        "print-uri",
59        "create-job",
60        "pause-printer",
61        "resume-printer",
62        "get-printer-attributes",
63        "set-printer-attributes",
64        "cancel-job",
65        "send-document",
66        "send-uri",
67        "get-job-attributes",
68        "set-job-attributes",
69        "restart-job",
70        "promote-job"
[f6e2532]71    ]
72       
[609a9b0]73    def __init__(self, name, config, *args, **kwargs):
[eee389a]74
[d21198f]75        super(GutenbachPrinter, self).__init__(*args, **kwargs)
76       
77        self.name = name
[609a9b0]78        self.config = config
[d21198f]79        self.time_created = int(time.time())
[b2e077a]80
[d21198f]81        self.finished_jobs = []
82        self.pending_jobs = []
83        self.current_job = None
84        self.jobs = {}
[b2e077a]85
[d21198f]86        self.lock = threading.RLock()
[33528b4]87        self._running = False
[d21198f]88        self.paused = False
[776a659]89
[d21198f]90        # CUPS ignores jobs with id 0, so we have to start at 1
91        self._next_job_id = 1
[b01b6d1]92
[33528b4]93    @sync
[b01b6d1]94    def __repr__(self):
95        return str(self)
96
[33528b4]97    @sync
[b01b6d1]98    def __str__(self):
[33528b4]99        return "<Printer '%s'>" % self.name
[b01b6d1]100
[33ea505]101    def run(self):
[33528b4]102        self._running = True
103        while self._running:
104            try:
105                with self.lock:
106                    if self.current_job is None:
[33ea505]107                        self.start_job()
[345c476]108                    elif self.current_job.is_done:
[33ea505]109                        self.complete_job()
[33528b4]110            except:
111                logger.fatal(traceback.format_exc())
112                sys.exit(1)
[33ea505]113            time.sleep(0.1)
114
[33528b4]115    def stop(self):
116        with self.lock:
117            self._running = False
118        if self.ident is not None and self.isAlive():
119            self.join()
120
[b01b6d1]121    ######################################################################
122    ###                          Properties                            ###
123    ######################################################################
124
125    @property
[7e29e6a]126    def name(self):
127        return self._name
128    @name.setter
129    def name(self, val):
130        try:
131            self._name = str(val)
132        except:
133            self._name = "gutenbach-printer"
134
135    @property
136    def config(self):
137        return self._config
138    @config.setter
139    def config(self, val):
140        try:
141            _config = dict(val).copy()
142        except:
143            raise ValueError, "not a dictionary"
144        if 'ipp-versions' not in _config:
145            raise ValueError, "missing ipp-versions"
146        self._config = _config
147
148    @property
[b01b6d1]149    def uris(self):
150        uris = ["ipp://localhost:8000/printers/" + self.name,
151                "ipp://localhost/printers/" + self.name]
152        return uris
153   
154    @property
155    def uri(self):
156        return self.uris[0]
157
158    @property
[cf0d7e8]159    @sync
[eee389a]160    def state(self):
[5e70cc2]161        if self.is_running and self.current_job is not None:
162            state = States.PROCESSING
163        elif self.is_running and len(self.pending_jobs) == 0:
164            state = States.IDLE
[cf0d7e8]165        else:
[5e70cc2]166            state = States.STOPPED
167        return state
[eee389a]168
169    @property
[cf0d7e8]170    @sync
[eee389a]171    def active_jobs(self):
[cf0d7e8]172        jobs = self.pending_jobs[:]
173        if self.current_job is not None:
174            jobs.insert(0, self.current_job.id)
[eee389a]175        return jobs
[b01b6d1]176
[33528b4]177    @property
178    def is_running(self):
179        running = self.ident is not None and self.isAlive() and self._running
180        return running
181
[b01b6d1]182    ######################################################################
183    ###                            Methods                             ###
184    ######################################################################
185
[cf0d7e8]186    @sync
[5e70cc2]187    def assert_running(self):
188        if not self.is_running:
189            raise RuntimeError, "%s not started" % str(self)
190
191    @sync
[eee389a]192    def start_job(self):
[5e70cc2]193        self.assert_running()
[33528b4]194        if not self.paused and self.current_job is None:
[cf0d7e8]195            try:
196                job_id = heapq.heappop(self.pending_jobs)
197                self.current_job = self.get_job(job_id)
198                self.current_job.play()
199            except IndexError:
200                self.current_job = None
201            except InvalidJobStateException:
202                heapq.heappush(self.pending_jobs, self.current_job.id)
203                self.current_job = None
[eee389a]204                   
[cf0d7e8]205    @sync
[eee389a]206    def complete_job(self):
[5e70cc2]207        self.assert_running()
[33528b4]208        if not self.paused and self.current_job is not None:
209            try:
210                if not self.current_job.is_done:
211                    self.current_job.stop()
212            finally:
213                self.finished_jobs.append(self.current_job.id)
214                self.current_job = None
[1a63bf7]215
[cf0d7e8]216    @sync
[eee389a]217    def get_job(self, job_id):
[5e70cc2]218        self.assert_running()
[cf0d7e8]219        if job_id not in self.jobs:
220            raise InvalidJobException(job_id)
221        return self.jobs[job_id]
[b01b6d1]222
223    ######################################################################
224    ###                        IPP Attributes                          ###
225    ######################################################################
[1a63bf7]226
[b2e077a]227    @property
228    def printer_uri_supported(self):
[5e70cc2]229        self.assert_running()
[793432f]230        return ipp.PrinterUriSupported(self.uri)
[9da7428]231    @printer_uri_supported.setter
232    def printer_uri_supported(self, val):
[5e70cc2]233        self.assert_running()
[9da7428]234        raise ipp.errors.AttributesNotSettable("printer-uri-supported")
[1a63bf7]235
[b2e077a]236    @property
237    def uri_authentication_supported(self):
[5e70cc2]238        self.assert_running()
[793432f]239        return ipp.UriAuthenticationSupported("none")
[9da7428]240    @uri_authentication_supported.setter
241    def uri_authentication_supported(self, val):
[5e70cc2]242        self.assert_running()
[9da7428]243        raise ipp.errors.AttributesNotSettable("uri-authentication-supported")
[b2e077a]244
245    @property
246    def uri_security_supported(self):
[5e70cc2]247        self.assert_running()
[793432f]248        return ipp.UriSecuritySupported("none")
[9da7428]249    @uri_security_supported.setter
250    def uri_security_supported(self, val):
[5e70cc2]251        self.assert_running()
[9da7428]252        raise ipp.errors.AttributesNotSettable("uri-security-supported")
[b2e077a]253
254    @property
255    def printer_name(self):
[5e70cc2]256        self.assert_running()
[793432f]257        return ipp.PrinterName(self.name)
[9da7428]258    @printer_name.setter
259    def printer_name(self, val):
[5e70cc2]260        self.assert_running()
[9da7428]261        raise ipp.errors.AttributesNotSettable("printer-name")
[1a63bf7]262
[b2e077a]263    @property
264    def printer_state(self):
[5e70cc2]265        self.assert_running()
[b01b6d1]266        return ipp.PrinterState(self.state)
[9da7428]267    @printer_state.setter
268    def printer_state(self, val):
[5e70cc2]269        self.assert_running()
[9da7428]270        raise ipp.errors.AttributesNotSettable("printer-state")
[1a63bf7]271
[b2e077a]272    @property
273    def printer_state_reasons(self):
[5e70cc2]274        self.assert_running()
[793432f]275        return ipp.PrinterStateReasons("none")
[9da7428]276    @printer_state_reasons.setter
277    def printer_state_reasons(self, val):
[5e70cc2]278        self.assert_running()
[9da7428]279        raise ipp.errors.AttributesNotSettable("printer-state-reasons")
[b2e077a]280
281    @property
282    def ipp_versions_supported(self):
[5e70cc2]283        self.assert_running()
[609a9b0]284        return ipp.IppVersionsSupported(*self.config['ipp-versions'])
[9da7428]285    @ipp_versions_supported.setter
286    def ipp_versions_supported(self, val):
[5e70cc2]287        self.assert_running()
[9da7428]288        raise ipp.errors.AttributesNotSettable("ipp-versions-supported")
[1a63bf7]289
[f6e2532]290    # XXX: We should query ourself for the supported operations
[b2e077a]291    @property
292    def operations_supported(self):
[5e70cc2]293        self.assert_running()
[793432f]294        return ipp.OperationsSupported(ipp.OperationCodes.GET_JOBS)
[9da7428]295    @operations_supported.setter
296    def operations_supported(self, val):
[5e70cc2]297        self.assert_running()
[9da7428]298        raise ipp.errors.AttributesNotSettable("operations-supported")
[b2e077a]299
300    @property
301    def charset_configured(self):
[5e70cc2]302        self.assert_running()
[9da7428]303        return ipp.CharsetConfigured("utf-8") # XXX
304    @charset_configured.setter
305    def charset_configured(self, val):
[5e70cc2]306        self.assert_running()
[9da7428]307        raise ipp.errors.AttributesNotSettable("charset-configured")
308       
[b2e077a]309    @property
310    def charset_supported(self):
[5e70cc2]311        self.assert_running()
[9da7428]312        return ipp.CharsetSupported("utf-8") # XXX
313    @charset_supported.setter
314    def charset_supported(self, val):
[5e70cc2]315        self.assert_running()
[9da7428]316        raise ipp.errors.AttributesNotSettable("charset-supported")
[b2e077a]317
318    @property
319    def natural_language_configured(self):
[5e70cc2]320        self.assert_running()
[793432f]321        return ipp.NaturalLanguageConfigured("en-us")
[9da7428]322    @natural_language_configured.setter
323    def natural_language_configured(self, val):
[5e70cc2]324        self.assert_running()
[9da7428]325        raise ipp.errors.AttributesNotSettable("natural-language-configured")
[b2e077a]326
327    @property
328    def generated_natural_language_supported(self):
[5e70cc2]329        self.assert_running()
[793432f]330        return ipp.GeneratedNaturalLanguageSupported("en-us")
[9da7428]331    @generated_natural_language_supported.setter
332    def generated_natural_language_supported(self, val):
[5e70cc2]333        self.assert_running()
[9da7428]334        raise ipp.errors.AttributesNotSettable("generated-natural-language-supported")
[b2e077a]335
336    @property
337    def document_format_default(self):
[5e70cc2]338        self.assert_running()
[793432f]339        return ipp.DocumentFormatDefault("application/octet-stream")
[9da7428]340    @document_format_default.setter
341    def document_format_default(self, val):
[5e70cc2]342        self.assert_running()
[9da7428]343        raise ipp.errors.AttributesNotSettable("document-format-default")
[b2e077a]344
345    @property
346    def document_format_supported(self):
[5e70cc2]347        self.assert_running()
[793432f]348        return ipp.DocumentFormatSupported("application/octet-stream", "audio/mp3")
[9da7428]349    @document_format_supported.setter
350    def document_format_supported(self, val):
[5e70cc2]351        self.assert_running()
[9da7428]352        raise ipp.errors.AttributesNotSettable("document-format-supported")
[b2e077a]353
354    @property
355    def printer_is_accepting_jobs(self):
[5e70cc2]356        self.assert_running()
[793432f]357        return ipp.PrinterIsAcceptingJobs(True)
[9da7428]358    @printer_is_accepting_jobs.setter
359    def printer_is_accepting_jobs(self, val):
[5e70cc2]360        self.assert_running()
[9da7428]361        raise ipp.errors.AttributesNotSettable("printer-is-accepting-jobs")
[b2e077a]362
363    @property
364    def queued_job_count(self):
[5e70cc2]365        self.assert_running()
[793432f]366        return ipp.QueuedJobCount(len(self.active_jobs))
[9da7428]367    @queued_job_count.setter
368    def queued_job_count(self, val):
[5e70cc2]369        self.assert_running()
[9da7428]370        raise ipp.errors.AttributesNotSettable("queued-job-count")
[b2e077a]371
372    @property
373    def pdl_override_supported(self):
[5e70cc2]374        self.assert_running()
[793432f]375        return ipp.PdlOverrideSupported("not-attempted")
[9da7428]376    @pdl_override_supported.setter
377    def pdl_override_supported(self, val):
[5e70cc2]378        self.assert_running()
[9da7428]379        raise ipp.errors.AttributesNotSettable("pdl-override-supported")
[b2e077a]380
381    @property
382    def printer_up_time(self):
[5e70cc2]383        self.assert_running()
[793432f]384        return ipp.PrinterUpTime(int(time.time()) - self.time_created)
[9da7428]385    @printer_up_time.setter
386    def printer_up_time(self, val):
[5e70cc2]387        self.assert_running()
[9da7428]388        raise ipp.errors.AttributesNotSettable("printer-up-time")
[b2e077a]389
390    @property
391    def compression_supported(self):
[5e70cc2]392        self.assert_running()
[793432f]393        return ipp.CompressionSupported("none")
[9da7428]394    @compression_supported.setter
395    def compression_supported(self, val):
[5e70cc2]396        self.assert_running()
[9da7428]397        raise ipp.errors.AttributesNotSettable("compression-supported")
[b2e077a]398
[f6e2532]399    @property
400    def multiple_operation_time_out(self):
[5e70cc2]401        self.assert_running()
[793432f]402        return ipp.MultipleOperationTimeOut(240)
[9da7428]403    @multiple_operation_time_out.setter
404    def multiple_operation_time_out(self, val):
[5e70cc2]405        self.assert_running()
[9da7428]406        raise ipp.errors.AttributesNotSettable("multiple-operation-time-out")
[f6e2532]407
408    @property
409    def multiple_document_jobs_supported(self):
[5e70cc2]410        self.assert_running()
[793432f]411        return ipp.MultipleDocumentJobsSupported(False)
[9da7428]412    @multiple_document_jobs_supported.setter
413    def multiple_document_jobs_supported(self, val):
[5e70cc2]414        self.assert_running()
[9da7428]415        raise ipp.errors.AttributesNotSettable("multiple-document-jobs-supported")
[f6e2532]416
[33ea505]417    ######################################################################
418    ###                      Job IPP Attributes                        ###
419    ######################################################################
420
421    def job_id(self, job_id):
[5e70cc2]422        self.assert_running()
[33ea505]423        job = self.get_job(job_id)
424        return ipp.JobId(job.id)
425
426    def job_name(self, job_id):
[5e70cc2]427        self.assert_running()
[33ea505]428        job = self.get_job(job_id)
429        return ipp.JobName(job.name)
430
431    def job_originating_user_name(self, job_id):
[5e70cc2]432        self.assert_running()
[33ea505]433        job = self.get_job(job_id)
434        return ipp.JobOriginatingUserName(job.creator)
435
436    def job_k_octets(self, job_id):
[5e70cc2]437        self.assert_running()
[33ea505]438        job = self.get_job(job_id)
439        return ipp.JobKOctets(job.size)
440
441    def job_state(self, job_id):
[5e70cc2]442        self.assert_running()
[33ea505]443        job = self.get_job(job_id)
444        return ipp.JobState(job.state)
445
446    def job_printer_uri(self, job_id):
[5e70cc2]447        self.assert_running()
[33ea505]448        job = self.get_job(job_id)
449        return ipp.JobPrinterUri(self.uri)
[ee8e6d0]450
[b01b6d1]451    ######################################################################
452    ###                        IPP Operations                          ###
453    ######################################################################
454
[5e70cc2]455    @sync
[57bc2dc]456    def print_job(self, document, document_name=None, document_format=None,
457                  document_natural_language=None, requesting_user_name=None,
458                  compression=None, job_name=None, job_k_octets=None):
[b01b6d1]459
[5e70cc2]460        self.assert_running()
461
[57bc2dc]462        # create the job
463        job_id = self.create_job(
464            requesting_user_name=requesting_user_name,
465            job_name=job_name,
466            job_k_octets=job_k_octets)
467       
468        # send the document
469        self.send_document(
470            job_id,
471            document,
472            document_name=document_name,
473            document_format=document_format,
474            document_natural_language=document_natural_language,
475            requesting_user_name=requesting_user_name,
476            compression=compression,
477            last_document=False)
478
479        return job_id
480
[5e70cc2]481    @sync
[7e29e6a]482    def validate_job(self, document_name=None, document_format=None,
483                     document_natural_language=None, requesting_user_name=None,
484                     compression=None, job_name=None, job_k_octets=None):
[57bc2dc]485
[5e70cc2]486        self.assert_running()
487
[57bc2dc]488        job_id = self._next_job_id
489        job = GutenbachJob(
490            job_id,
491            creator=requesting_user_name,
492            name=job_name)
493        job.spool(tempfile.TemporaryFile())
494        job.abort()
495        del job
[b01b6d1]496
[5e70cc2]497    @sync
[33ea505]498    def get_jobs(self, requesting_user_name=None, which_jobs=None,
499                 requested_attributes=None):
500       
[5e70cc2]501        self.assert_running()
502
[b01b6d1]503        # Filter by the which-jobs attribute
504        if which_jobs is None:
[34a4e5d]505            which_jobs = "not-completed"
506
507        if which_jobs == "completed":
[b01b6d1]508            jobs = [self.jobs[job_id] for job_id in self.finished_jobs]
509        elif which_jobs == "not-completed":
510            jobs = [self.jobs[job_id] for job_id in self.active_jobs]
[ee8e6d0]511        else:
[b01b6d1]512            raise ipp.errors.ClientErrorAttributes(
513                which_jobs, ipp.WhichJobs(which_jobs))
[b2e077a]514
[b01b6d1]515        # Filter by username
516        if requesting_user_name is None:
517            user_jobs = jobs
518        else:
519            user_jobs = [job for job in jobs if job.creator == requesting_user_name]
[33ea505]520
521        # Get the attributes of each job
522        job_attrs = [self.get_job_attributes(
523            job.id, requested_attributes=requested_attributes) for job in user_jobs]
[ee8e6d0]524       
[33ea505]525        return job_attrs
[ee8e6d0]526
[5e70cc2]527    @sync
[b01b6d1]528    def print_uri(self):
[5e70cc2]529        self.assert_running()
[b01b6d1]530
[5e70cc2]531    @sync
[57bc2dc]532    def create_job(self, requesting_user_name=None, job_name=None,
533                   job_k_octets=None):
534
[5e70cc2]535        self.assert_running()
536
[eee389a]537        job_id = self._next_job_id
538        self._next_job_id += 1
[ee8e6d0]539       
[33ea505]540        job = GutenbachJob(
541            job_id,
542            creator=requesting_user_name,
543            name=job_name)
[57bc2dc]544
[ee8e6d0]545        self.jobs[job_id] = job
[eee389a]546        self.pending_jobs.append(job_id)
[b01b6d1]547       
[33ea505]548        return job_id
[776a659]549
[fa3e2c6]550    @sync
[b01b6d1]551    def pause_printer(self):
[fa3e2c6]552        """Pause the printer.
553
554        Does nothing if the printer is already paused.
555        """
[33528b4]556       
[5e70cc2]557        self.assert_running()
[33528b4]558        if not self.paused:
559            if self.current_job is not None and self.current_job.is_playing:
560                self.current_job.pause()
561            self.paused = True
562            logger.info("%s paused", str(self))
[fa3e2c6]563
564    @sync
[b01b6d1]565    def resume_printer(self):
[fa3e2c6]566        """Resume the printer.
567
568        Does nothing if the printer is not paused.
569        """
[33528b4]570       
[5e70cc2]571        self.assert_running()
[33528b4]572        if self.paused:
573            if self.current_job is not None:
574                self.current_job.resume()
575            self.paused = False
576            logger.info("%s unpaused", str(self))
[776a659]577
[57bc2dc]578    @sync
[b01b6d1]579    def get_printer_attributes(self, requested_attributes=None):
[5e70cc2]580        self.assert_running()
[b01b6d1]581        if requested_attributes is None:
[33ea505]582            requested = self.printer_attributes
[e58af05]583        else:
[33ea505]584            requested = [a for a in self.printer_attributes \
585                         if a in requested_attributes]
[b2e077a]586
[b01b6d1]587        _attributes = [attr.replace("-", "_") for attr in requested]
588        attributes = [getattr(self, attr) for attr in _attributes]
589        return attributes
[776a659]590
[57bc2dc]591    @sync
[7e29e6a]592    def set_printer_attributes(self, attributes):
[5e70cc2]593        self.assert_running()
[9da7428]594        for attr in attributes:
595            try:
596                setattr(self, attr, attributes[attr])
597            except AttributeError:
598                raise ipp.errors.ClientErrorAttributes
[33ea505]599
[57bc2dc]600    @sync
[33ea505]601    def cancel_job(self, job_id, requesting_user_name=None):
[5e70cc2]602        self.assert_running()
[33ea505]603        job = self.get_job(job_id)
604        try:
605            job.cancel()
606        except InvalidJobStateException:
607            # XXX
608            raise
609
[57bc2dc]610    @sync
[33ea505]611    def send_document(self, job_id, document, document_name=None,
612                      document_format=None, document_natural_language=None,
613                      requesting_user_name=None, compression=None,
614                      last_document=None):
615
[5e70cc2]616        self.assert_running()
[33ea505]617        job = self.get_job(job_id)
[345c476]618        job.spool(document)
[33ea505]619
[57bc2dc]620    @sync
[c1dc25f]621    def send_uri(self, job_id, document_uri, document_name=None,
622                 document_format=None, document_natural_language=None,
623                 requesting_user_name=None, compression=None,
624                 last_document=None):
[5e70cc2]625
626        self.assert_running()
[c1dc25f]627        job = self.get_job(job_id)
628        # XXX: need to validate URI
629        # XXX: need to deal with the URI stream?
630        #job.spool_uri(document_uri)
[33ea505]631
[57bc2dc]632    @sync
[33ea505]633    def get_job_attributes(self, job_id, requested_attributes=None):
[5e70cc2]634
635        self.assert_running()
[33ea505]636        if requested_attributes is None:
637            requested = self.job_attributes
638        else:
639            requested = [a for a in self.job_attributes \
640                         if a in requested_attributes]
641
642        _attributes = [attr.replace("-", "_") for attr in requested]
643        attributes = [getattr(self, attr)(job_id) for attr in _attributes]
644        return attributes
645
[57bc2dc]646    @sync
[9da7428]647    def set_job_attributes(self, job_id, attributes):
[5e70cc2]648
649        self.assert_running()
[9da7428]650        job = self.get_job(job_id)
651        for attr in attributes:
652            if attr in ("job-id", "job-k-octets", "job-state", "job-printer-uri"):
653                raise ipp.errors.ClientErrorAttributesNotSettable(attr)
654            elif attr == "job-name":
655                job.name = attributes[attr]
656            elif attr == "job-originating-user-name":
657                job.creator = attributes[attr] # XXX: do we want this?
[57bc2dc]658
659    @sync
[c1cebbc]660    def restart_job(self, job_id, requesting_user_name=None):
[5e70cc2]661
662        self.assert_running()
[c1cebbc]663        job = self.get_job(job_id)
664        try:
665            job.restart()
666        except InvalidJobStateException:
667            # XXX
668            raise ipp.errors.ClientErrorNotPossible
[33ea505]669
[57bc2dc]670        self.finished_jobs.remove(job_id)
671        self.pending_jobs.append(job_id)
[33ea505]672
[57bc2dc]673    @sync
[c1cebbc]674    def promote_job(self, job_id, requesting_user_name=None):
[c500bc2]675        # According to RFC 3998, we need to put the job at the front
676        # of the queue (so that when the currently playing job
677        # completes, this one will go next
678       
[5e70cc2]679        self.assert_running()
[c500bc2]680        job = self.get_job(job_id)
681        job.priority = 1 # XXX we need to actually do something
682                         # correct here
Note: See TracBrowser for help on using the repository browser.