source: server/lib/gutenbach/server/job.py @ 951ab1b

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

Add support for verifying documents in job.py

  • Property mode set to 100644
File size: 9.0 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
[776a659]16        """
[ee8e6d0]17
[b01b6d1]18        self.player = None
[345c476]19        self.document = None
[ee8e6d0]20
[b01b6d1]21        self.id = job_id
22        self.creator = creator
23        self.name = name
[345c476]24        self.priority = priority
25
26        if document is not None:
27            self.spool(document)
[ee8e6d0]28
[951ab1b]29        self._why_done = None
30
[b01b6d1]31    def __repr__(self):
32        return str(self)
33
34    def __str__(self):
35        return "<Job %d '%s'>" % (self.id, self.name)
[e58af05]36
[eee389a]37    def __cmp__(self, other):
38        return cmp(self.priority, other.priority)
39
[b01b6d1]40    ######################################################################
41    ###                          Properties                            ###
42    ######################################################################
[1a63bf7]43
[b01b6d1]44    @property
45    def id(self):
46        """Unique job identifier.  Should be a positive integer,
47        except when unassigned, when it defaults to -1.
48       
49        """
50        return self._id
51    @id.setter
52    def id(self, val):
[1a63bf7]53        try:
[345c476]54            self._id = max(int(val), -1)
[951ab1b]55        except:
[b01b6d1]56            self._id = -1
57
58    @property
[345c476]59    def priority(self):
60        return self._priority
61    @priority.setter
62    def priority(self, val):
63        try:
64            self._priority = max(int(val), 1)
[951ab1b]65        except:
[345c476]66            self._priority = 1
67
68    @property
[b01b6d1]69    def creator(self):
70        """The user who created the job; analogous to the IPP
71        requesting-user-name.
72
73        """
74        return self._creator
75    @creator.setter
76    def creator(self, val):
77        if val is None:
78            self._creator = ""
79        else:
80            self._creator = str(val)
81
82    @property
83    def name(self):
84        """The job's name.
85
86        """
87        return self._name
88    @name.setter
89    def name(self, val):
90        if val is None:
91            self._name = ""
92        else:
93            self._name = str(val)
94
95    @property
96    def size(self):
97        """The size of the job in bytes.
[1a63bf7]98
[b01b6d1]99        """
[ee8e6d0]100        try:
[33ea505]101            size = os.path.getsize(self.document)
102        except:
103            size = 0
104        return size
[b01b6d1]105
[951ab1b]106    ######################################################################
107    ###                            State                               ###
108    ######################################################################
109
[eee389a]110    @property
[345c476]111    def is_valid(self):
112        """Whether the job is ready to be manipulated (spooled,
113        played, etc).  Note that playing the job still requires it to
114        be spooled first.
115
116        """
117        return self.id > 0 and \
118               self.priority > 0
[eee389a]119
120    @property
121    def is_ready(self):
[345c476]122        """Whether the job is ready to be played.
123
124        """
125        return self.is_valid and \
126               self.player is not None and \
127               not self.player.is_playing and \
128               not self._why_done == "cancelled" and \
129               not self._why_done == "aborted"
[eee389a]130
131    @property
[345c476]132    def is_playing(self):
133        """Whether the job is currently playing (regardless of whether
134        it's paused).
135
136        """
137        return self.is_valid and \
138               self.player is not None and \
139               self.player.is_playing
140
141    @property
142    def is_paused(self):
143        """Whether the job is currently paused.
144
145        """
146        return self.is_valid and \
147               self.player is not None and \
148               self.player.is_paused       
149
150    @property
151    def is_done(self):
[951ab1b]152        """Whether the job is done playing, regardless of whether it
153        completed successfully or not.
154
155        """
[345c476]156        return (self.is_valid and \
157                self.player is not None and \
158                self.player.is_done) or \
159                (self._why_done == "cancelled" or \
160                 self._why_done == "aborted")
161
162    @property
[951ab1b]163    def is_completed(self):
164        """Whether the job completed successfully.
165
166        """
167        return self.is_done and self._why_done == "completed"
168
169    @property
170    def is_cancelled(self):
171        """Whether the job was cancelled.
172
173        """
174        return self.is_done and self._why_done == "cancelled"
175
176    @property
177    def is_aborted(self):
178        """Whether the job was aborted.
179
180        """
181        return self.is_done and self._why_done == "aborted"
182
183    @property
[345c476]184    def state(self):
[a2b0582]185        """
186        State transitions are as follows:
187HELD ---> PENDING ---> PROCESSING <--> STOPPED (aka paused)
188             ^              |---> CANCELLED
189             |              |---> ABORTED
190             |              |---> COMPLETE ---|
191             |--------------------------------|
192        """
[345c476]193        if self.is_ready:
194            state = States.PENDING
195        elif self.is_playing and not self.is_paused:
196            state = States.PROCESSING
197        elif self.is_playing and self.is_paused:
198            state = States.STOPPED
[951ab1b]199        elif self.is_completed:
[345c476]200            state = States.COMPLETE
[951ab1b]201        elif self.is_cancelled:
[345c476]202            state = States.CANCELLED
[951ab1b]203        elif self.is_aborted:
[345c476]204            state = States.ABORTED
205        else:
206            state = States.HELD
207        return state
208
[b01b6d1]209    ######################################################################
210    ###                            Methods                             ###
211    ######################################################################
212
[951ab1b]213    @staticmethod
214    def verify_document(document):
215        if not hasattr(document, "name"):
216            raise errors.InvalidDocument, "no name attribute"
217        if not hasattr(document, "read"):
218            raise errors.InvalidDocument, "no read attribute"
219        if not hasattr(document, "close"):
220            raise errors.InvalidDocument, "no close attribute"
221
222    def spool(self, document=None):
223        """Non-blocking spool.  Job must be valid, and the document
224        must be an open file handler.
225
226        Raises
227        ------
228        InvalidDocument
229            If the document is not valid.
230        InvalidJobStateException
231            If the job is not valid or it is already
232            spooled/ready/finished.
233
234        """
235
236        if not self.is_valid or self.state != States.HELD:
237            raise errors.InvalidJobStateException(self.state)
238        self.verify_document(document)
[33ea505]239        self.document = document.name
240        self.player = Player(document)
241        logger.debug("document for job %d is '%s'" % (self.id, self.document))
242
[b01b6d1]243    def play(self):
[951ab1b]244        """Non-blocking play.  Job must be ready.
[33ea505]245
246        Raises
247        ------
248        InvalidJobStateException
249            If the job is not ready to be played.
[eee389a]250
251        """
252       
253        # make sure the job is waiting to be played and that it's
254        # valid
[33ea505]255        if not self.is_ready:
[951ab1b]256            raise errors.InvalidJobStateException(self.state)
[eee389a]257       
258        # and set the state to processing if we're good to go
[b01b6d1]259        logger.info("playing job %s" % str(self))
[33ea505]260
261        def _completed():
262            logger.info("completed job %s" % str(self))
[345c476]263            self._why_done = "completed"
[33ea505]264        self.player.callback = _completed
[d21198f]265        self.player.start()
[eee389a]266
267    def pause(self):
[951ab1b]268        """Non-blocking pause.  Job must be playing.
269
270        Raises
271        ------
272        InvalidJobStateException
273            If the job is not playing.
[33ea505]274
275        """
276       
277        if not self.is_playing:
[951ab1b]278            raise errors.InvalidJobStateException(self.state)
[33ea505]279        self.player.mplayer_pause()
[34a4e5d]280
281    def cancel(self):
[951ab1b]282        """Non-blocking cancel. The job must not have previously
283        finished (i.e., cannot be aborted, cancelled, or completed).
284        This should be used to stop the job following an external
285        request.
286
287        Raises
288        ------
289        InvalidJobStateException
290            If the job has already finished.
291
292        """
293       
[345c476]294        def _cancelled():
295            logger.info("cancelled job %s" % str(self))
296            self._why_done = "cancelled"
[33ea505]297
[34a4e5d]298        if self.is_playing:
[345c476]299            self.player.callback = _cancelled
[34a4e5d]300            self.player.mplayer_stop()
[345c476]301        elif self.is_done and not self._why_done == "cancelled":
[951ab1b]302            raise errors.InvalidJobStateException(self.state)
[345c476]303        else:
304            _cancelled()
[eee389a]305
[34a4e5d]306    def abort(self):
[951ab1b]307        """Non-blocking abort. The job must not have previously
308        finished (i.e., cannot be aborted, cancelled, or completed).
309        This should be used to stop the job following internal errors.
310
311        Raises
312        ------
313        InvalidJobStateException
314            If the job has already finished.
315
316        """
317
[33ea505]318        def _aborted():
319            logger.info("aborted job %s" % str(self))
[345c476]320            self._why_done = "aborted"
[33ea505]321
[34a4e5d]322        if self.is_playing:
[33ea505]323            self.player.callback = _aborted
[eee389a]324            self.player.mplayer_stop()
[345c476]325        elif self.is_done and not self._why_done == "aborted":
[951ab1b]326            raise errors.InvalidJobStateException(self.state)
[345c476]327        else:
328            _aborted()
[776a659]329
330
Note: See TracBrowser for help on using the repository browser.