source: server/lib/gutenbach/server/job.py @ 57bc2dc

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

Add support for print-job and verify-job in printer.py

  • Property mode set to 100644
File size: 11.6 KB
RevLine 
[951ab1b]1from . import errors
[eee389a]2from .player import Player
[b01b6d1]3from gutenbach.ipp import JobStates as States
[ee8e6d0]4import logging
[33ea505]5import os
[d04a689]6
7# initialize logger
8logger = logging.getLogger(__name__)
[776a659]9
[33ea505]10class GutenbachJob(object):
[b2e077a]11
[345c476]12    def __init__(self, job_id=None, creator=None, name=None,
13                 priority=None, document=None):
[b01b6d1]14        """Create an empty Gutenbach job.
[e58af05]15
[ef387cf]16        Parameters
17        ----------
18        job_id : integer
19            A unique id for this job.
20        creator : string
21            The user creating the job.
22        name : string
23            The human-readable name of the job.
24        priority : integer
25            The priority of the job, used for ordering.
26        document : file object
27            A file object containing the job data.
28
[776a659]29        """
[ee8e6d0]30
[b01b6d1]31        self.player = None
[345c476]32        self.document = None
[ee8e6d0]33
[b01b6d1]34        self.id = job_id
35        self.creator = creator
36        self.name = name
[345c476]37        self.priority = priority
[ef387cf]38       
[2620618]39        self._why_done = None
[345c476]40
41        if document is not None:
42            self.spool(document)
[ee8e6d0]43
[b01b6d1]44    def __repr__(self):
45        return str(self)
46
47    def __str__(self):
48        return "<Job %d '%s'>" % (self.id, self.name)
[e58af05]49
[eee389a]50    def __cmp__(self, other):
[ef387cf]51        """Compares two GutenbachJobs based on their priorities.
52
53        """
[eee389a]54        return cmp(self.priority, other.priority)
55
[57bc2dc]56    def __del__(self):
57        if self.player:
58            self.player.mplayer_stop()
59            self.player = None
60        if self.document and not self.document.closed:
61            self.document.close()
62            self.document = None
63
64        self.id = None
65        self.creator = None
66        self.name = None
67        self.priority = None
68        self._why_done = None
69
[b01b6d1]70    ######################################################################
71    ###                          Properties                            ###
72    ######################################################################
[1a63bf7]73
[b01b6d1]74    @property
75    def id(self):
[ef387cf]76        """Unique job identifier (integer).  Should be a positive
77        integer, except when unassigned, when it defaults to -1.
[b01b6d1]78       
79        """
80        return self._id
81    @id.setter
82    def id(self, val):
[1a63bf7]83        try:
[345c476]84            self._id = max(int(val), -1)
[951ab1b]85        except:
[b01b6d1]86            self._id = -1
87
88    @property
[345c476]89    def priority(self):
[ef387cf]90        """Job priority (integer).  Should be a nonzero positive
91        integer; defaults to 1 when unassigned.
92
93        """
[345c476]94        return self._priority
95    @priority.setter
96    def priority(self, val):
97        try:
98            self._priority = max(int(val), 1)
[951ab1b]99        except:
[345c476]100            self._priority = 1
101
102    @property
[b01b6d1]103    def creator(self):
[ef387cf]104        """The user who created the job (string).  Defaults to an
105        empty string.
[b01b6d1]106
107        """
108        return self._creator
109    @creator.setter
110    def creator(self, val):
111        if val is None:
112            self._creator = ""
113        else:
114            self._creator = str(val)
115
116    @property
117    def name(self):
[ef387cf]118        """The job's human-readable name (string).  Defaults to an
119        empty string.
[b01b6d1]120
121        """
122        return self._name
123    @name.setter
124    def name(self, val):
125        if val is None:
126            self._name = ""
127        else:
128            self._name = str(val)
129
130    @property
131    def size(self):
[ef387cf]132        """The size of the job in octets/bytes (integer).  Defaults to
133        0 if no document is specified or if there is an error reading
134        the document.
[1a63bf7]135
[b01b6d1]136        """
[ee8e6d0]137        try:
[33ea505]138            size = os.path.getsize(self.document)
139        except:
140            size = 0
141        return size
[b01b6d1]142
[951ab1b]143    ######################################################################
144    ###                            State                               ###
145    ######################################################################
146
[eee389a]147    @property
[345c476]148    def is_valid(self):
149        """Whether the job is ready to be manipulated (spooled,
150        played, etc).  Note that playing the job still requires it to
151        be spooled first.
152
153        """
[ef387cf]154
[345c476]155        return self.id > 0 and \
156               self.priority > 0
[eee389a]157
158    @property
159    def is_ready(self):
[ef387cf]160        """Whether the job is ready to be played; i.e., it has all the
161        necessary data to actually play the audio data.
[345c476]162
163        """
[ef387cf]164
[345c476]165        return self.is_valid and \
166               self.player is not None and \
167               not self.player.is_playing and \
168               not self._why_done == "cancelled" and \
169               not self._why_done == "aborted"
[eee389a]170
171    @property
[345c476]172    def is_playing(self):
173        """Whether the job is currently playing (regardless of whether
174        it's paused).
175
176        """
[ef387cf]177
[345c476]178        return self.is_valid and \
179               self.player is not None and \
180               self.player.is_playing
181
182    @property
183    def is_paused(self):
184        """Whether the job is currently paused.
185
186        """
[ef387cf]187
[345c476]188        return self.is_valid and \
189               self.player is not None and \
190               self.player.is_paused       
191
192    @property
193    def is_done(self):
[951ab1b]194        """Whether the job is done playing, regardless of whether it
195        completed successfully or not.
196
197        """
[ef387cf]198
[345c476]199        return (self.is_valid and \
200                self.player is not None and \
201                self.player.is_done) or \
202                (self._why_done == "cancelled" or \
203                 self._why_done == "aborted")
204
205    @property
[951ab1b]206    def is_completed(self):
207        """Whether the job completed successfully.
208
209        """
[ef387cf]210
[951ab1b]211        return self.is_done and self._why_done == "completed"
212
213    @property
214    def is_cancelled(self):
215        """Whether the job was cancelled.
216
217        """
[ef387cf]218
[951ab1b]219        return self.is_done and self._why_done == "cancelled"
220
221    @property
222    def is_aborted(self):
223        """Whether the job was aborted.
224
225        """
[ef387cf]226
[951ab1b]227        return self.is_done and self._why_done == "aborted"
228
229    @property
[345c476]230    def state(self):
[ef387cf]231        """State status codes (these are equivalent to the IPP
232        job-state status codes).  State transitions are as follows:
[2620618]233       
[ef387cf]234            HELD ---> PENDING ---> PROCESSING <--> STOPPED (aka paused)
235                         ^              |---> CANCELLED
236                         |              |---> ABORTED
237                         |              |---> COMPLETE ---|
238                         |--------------------------------|
[2620618]239                     
[a2b0582]240        """
[ef387cf]241
[345c476]242        if self.is_ready:
243            state = States.PENDING
244        elif self.is_playing and not self.is_paused:
245            state = States.PROCESSING
246        elif self.is_playing and self.is_paused:
247            state = States.STOPPED
[951ab1b]248        elif self.is_completed:
[345c476]249            state = States.COMPLETE
[951ab1b]250        elif self.is_cancelled:
[345c476]251            state = States.CANCELLED
[951ab1b]252        elif self.is_aborted:
[345c476]253            state = States.ABORTED
254        else:
255            state = States.HELD
256        return state
257
[b01b6d1]258    ######################################################################
259    ###                            Methods                             ###
260    ######################################################################
261
[951ab1b]262    @staticmethod
263    def verify_document(document):
[ef387cf]264        """Verifies that a document has the 'name', 'read', and
265        'close' attributes (i.e., it should be like a file object).
266
267        """
268       
[951ab1b]269        if not hasattr(document, "name"):
270            raise errors.InvalidDocument, "no name attribute"
271        if not hasattr(document, "read"):
272            raise errors.InvalidDocument, "no read attribute"
273        if not hasattr(document, "close"):
274            raise errors.InvalidDocument, "no close attribute"
[57bc2dc]275        if not hasattr(document, "closed"):
276            raise errors.InvalidDocument, "no closed attribute"
[951ab1b]277
278    def spool(self, document=None):
[ef387cf]279        """Non-blocking spool.  Job must be valid (see
280        'GutenbachJob.is_valid'), and the document must be an open
281        file handler.
[951ab1b]282
283        Raises
284        ------
285        InvalidDocument
286            If the document is not valid.
287        InvalidJobStateException
288            If the job is not valid or it is already
289            spooled/ready/finished.
290
291        """
292
293        if not self.is_valid or self.state != States.HELD:
294            raise errors.InvalidJobStateException(self.state)
295        self.verify_document(document)
[33ea505]296        self.document = document.name
297        self.player = Player(document)
298        logger.debug("document for job %d is '%s'" % (self.id, self.document))
299
[b01b6d1]300    def play(self):
[ef387cf]301        """Non-blocking play.  Job must be ready (see
302        'GutenbachJob.is_ready').
[33ea505]303
304        Raises
305        ------
306        InvalidJobStateException
307            If the job is not ready to be played.
[eee389a]308
309        """
310       
311        # make sure the job is waiting to be played and that it's
312        # valid
[33ea505]313        if not self.is_ready:
[951ab1b]314            raise errors.InvalidJobStateException(self.state)
[eee389a]315       
316        # and set the state to processing if we're good to go
[b01b6d1]317        logger.info("playing job %s" % str(self))
[33ea505]318
319        def _completed():
320            logger.info("completed job %s" % str(self))
[345c476]321            self._why_done = "completed"
[33ea505]322        self.player.callback = _completed
[d21198f]323        self.player.start()
[eee389a]324
325    def pause(self):
[ef387cf]326        """Non-blocking pause.  Job must be playing (see
327        'GutenbachJob.is_playing').
[951ab1b]328
329        Raises
330        ------
331        InvalidJobStateException
332            If the job is not playing.
[33ea505]333
334        """
335       
336        if not self.is_playing:
[951ab1b]337            raise errors.InvalidJobStateException(self.state)
[33ea505]338        self.player.mplayer_pause()
[34a4e5d]339
[cb0195f]340    def resume(self):
341        """Non-blocking resume.  Job must be paused (see
342        'GutenbachJob.is_paused').
343
344        Raises
345        ------
346        InvalidJobStateException
347            If the job is not paused.
348
349        """
350        if not self.is_paused:
351            raise errors.InvalidJobStateException(self.state)
352        self.player.mplayer_pause()
353
[34a4e5d]354    def cancel(self):
[b8c3505]355        """Blocking cancel. The job must not have been previously
[ef387cf]356        aborted or completed (though this method will succeed if it
357        was previously cancelled).  This should be used to stop the
358        job following an external request.
[951ab1b]359
360        Raises
361        ------
362        InvalidJobStateException
363            If the job has already finished.
364
365        """
366       
[34a4e5d]367        if self.is_playing:
[b8c3505]368            self.player._callback = None
[34a4e5d]369            self.player.mplayer_stop()
[b8c3505]370
[345c476]371        elif self.is_done and not self._why_done == "cancelled":
[951ab1b]372            raise errors.InvalidJobStateException(self.state)
[b8c3505]373
374        logger.info("cancelled job %s" % str(self))
375        self._why_done = "cancelled"
[eee389a]376
[34a4e5d]377    def abort(self):
[b8c3505]378        """Blocking abort. The job must not have been previously
[ef387cf]379        cancelled or completed (though this method will succeed if it
380        was previously aborted).  This should be used to stop the job
381        following internal errors.
[951ab1b]382
383        Raises
384        ------
385        InvalidJobStateException
386            If the job has already finished.
387
388        """
389
[34a4e5d]390        if self.is_playing:
[b8c3505]391            self.player._callback = None
[eee389a]392            self.player.mplayer_stop()
[b8c3505]393
[345c476]394        elif self.is_done and not self._why_done == "aborted":
[951ab1b]395            raise errors.InvalidJobStateException(self.state)
[b8c3505]396
397        logger.info("aborted job %s" % str(self))
398        self._why_done = "aborted"
[776a659]399
[609a9b0]400    def restart(self):
[c1cebbc]401        """Non-blocking restart.  Job must be finished (see
402        'GutenbachJob.is_done'), and will be ready to be played (see
403        'GutenbachJob.is_ready') if this method is successful.
404
405        Raises
406        ------
407        InvalidJobStateException
408            If the job is not done.
409
410        """
411
412        if not self.is_done:
413            raise errors.InvalidJobStateException(self.state)
414
415        logger.debug("restarting job %d" % (self.id, self.document))
416
417        self._why_done = None
418        self.spool(self.document)
Note: See TracBrowser for help on using the repository browser.