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

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

Add more documentation to job.py

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