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

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

Fix job destructor

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