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

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

Add restart job support to printer.py and job.py

  • Property mode set to 100644
File size: 10.8 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        """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        if self.is_playing:
338            self.player._callback = None
339            self.player.mplayer_stop()
340
341        elif self.is_done and not self._why_done == "cancelled":
342            raise errors.InvalidJobStateException(self.state)
343
344        logger.info("cancelled job %s" % str(self))
345        self._why_done = "cancelled"
346
347    def abort(self):
348        """Blocking abort. The job must not have been previously
349        cancelled or completed (though this method will succeed if it
350        was previously aborted).  This should be used to stop the job
351        following internal errors.
352
353        Raises
354        ------
355        InvalidJobStateException
356            If the job has already finished.
357
358        """
359
360        if self.is_playing:
361            self.player._callback = None
362            self.player.mplayer_stop()
363
364        elif self.is_done and not self._why_done == "aborted":
365            raise errors.InvalidJobStateException(self.state)
366
367        logger.info("aborted job %s" % str(self))
368        self._why_done = "aborted"
369
370    def restart(self):
371        """Non-blocking restart.  Job must be finished (see
372        'GutenbachJob.is_done'), and will be ready to be played (see
373        'GutenbachJob.is_ready') if this method is successful.
374
375        Raises
376        ------
377        InvalidJobStateException
378            If the job is not done.
379
380        """
381
382        if not self.is_done:
383            raise errors.InvalidJobStateException(self.state)
384
385        logger.debug("restarting job %d" % (self.id, self.document))
386
387        self._why_done = None
388        self.spool(self.document)
Note: See TracBrowser for help on using the repository browser.