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

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

Remove lag times from job test cases and put them in the actual player.

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