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
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    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
70    ######################################################################
71    ###                          Properties                            ###
72    ######################################################################
73
74    @property
75    def id(self):
76        """Unique job identifier (integer).  Should be a positive
77        integer, except when unassigned, when it defaults to -1.
78       
79        """
80        return self._id
81    @id.setter
82    def id(self, val):
83        try:
84            self._id = max(int(val), -1)
85        except:
86            self._id = -1
87
88    @property
89    def priority(self):
90        """Job priority (integer).  Should be a nonzero positive
91        integer; defaults to 1 when unassigned.
92
93        """
94        return self._priority
95    @priority.setter
96    def priority(self, val):
97        try:
98            self._priority = max(int(val), 1)
99        except:
100            self._priority = 1
101
102    @property
103    def creator(self):
104        """The user who created the job (string).  Defaults to an
105        empty string.
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):
118        """The job's human-readable name (string).  Defaults to an
119        empty string.
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):
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.
135
136        """
137        try:
138            size = os.path.getsize(self.document)
139        except:
140            size = 0
141        return size
142
143    ######################################################################
144    ###                            State                               ###
145    ######################################################################
146
147    @property
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        """
154
155        return self.id > 0 and \
156               self.priority > 0
157
158    @property
159    def is_ready(self):
160        """Whether the job is ready to be played; i.e., it has all the
161        necessary data to actually play the audio data.
162
163        """
164
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"
170
171    @property
172    def is_playing(self):
173        """Whether the job is currently playing (regardless of whether
174        it's paused).
175
176        """
177
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        """
187
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):
194        """Whether the job is done playing, regardless of whether it
195        completed successfully or not.
196
197        """
198
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
206    def is_completed(self):
207        """Whether the job completed successfully.
208
209        """
210
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        """
218
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        """
226
227        return self.is_done and self._why_done == "aborted"
228
229    @property
230    def state(self):
231        """State status codes (these are equivalent to the IPP
232        job-state status codes).  State transitions are as follows:
233       
234            HELD ---> PENDING ---> PROCESSING <--> STOPPED (aka paused)
235                         ^              |---> CANCELLED
236                         |              |---> ABORTED
237                         |              |---> COMPLETE ---|
238                         |--------------------------------|
239                     
240        """
241
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
248        elif self.is_completed:
249            state = States.COMPLETE
250        elif self.is_cancelled:
251            state = States.CANCELLED
252        elif self.is_aborted:
253            state = States.ABORTED
254        else:
255            state = States.HELD
256        return state
257
258    ######################################################################
259    ###                            Methods                             ###
260    ######################################################################
261
262    @staticmethod
263    def verify_document(document):
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       
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"
275        if not hasattr(document, "closed"):
276            raise errors.InvalidDocument, "no closed attribute"
277
278    def spool(self, document=None):
279        """Non-blocking spool.  Job must be valid (see
280        'GutenbachJob.is_valid'), and the document must be an open
281        file handler.
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)
296        self.document = document.name
297        self.player = Player(document)
298        logger.debug("document for job %d is '%s'" % (self.id, self.document))
299
300    def play(self):
301        """Non-blocking play.  Job must be ready (see
302        'GutenbachJob.is_ready').
303
304        Raises
305        ------
306        InvalidJobStateException
307            If the job is not ready to be played.
308
309        """
310       
311        # make sure the job is waiting to be played and that it's
312        # valid
313        if not self.is_ready:
314            raise errors.InvalidJobStateException(self.state)
315       
316        # and set the state to processing if we're good to go
317        logger.info("playing job %s" % str(self))
318
319        def _completed():
320            logger.info("completed job %s" % str(self))
321            self._why_done = "completed"
322        self.player.callback = _completed
323        self.player.start()
324
325    def pause(self):
326        """Non-blocking pause.  Job must be playing (see
327        'GutenbachJob.is_playing').
328
329        Raises
330        ------
331        InvalidJobStateException
332            If the job is not playing.
333
334        """
335       
336        if not self.is_playing:
337            raise errors.InvalidJobStateException(self.state)
338        self.player.mplayer_pause()
339
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
354    def cancel(self):
355        """Blocking cancel. The job must not have been previously
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.
359
360        Raises
361        ------
362        InvalidJobStateException
363            If the job has already finished.
364
365        """
366       
367        if self.is_playing:
368            self.player._callback = None
369            self.player.mplayer_stop()
370
371        elif self.is_done and not self._why_done == "cancelled":
372            raise errors.InvalidJobStateException(self.state)
373
374        logger.info("cancelled job %s" % str(self))
375        self._why_done = "cancelled"
376
377    def abort(self):
378        """Blocking abort. The job must not have been previously
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.
382
383        Raises
384        ------
385        InvalidJobStateException
386            If the job has already finished.
387
388        """
389
390        if self.is_playing:
391            self.player._callback = None
392            self.player.mplayer_stop()
393
394        elif self.is_done and not self._why_done == "aborted":
395            raise errors.InvalidJobStateException(self.state)
396
397        logger.info("aborted job %s" % str(self))
398        self._why_done = "aborted"
399
400    def restart(self):
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.