source: server/lib/gutenbach/server/job.py @ bd5bffc

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

Fix logger issues in job and player

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