2 # This file is part of mp3togo
4 # Convert audio files to play on a mp3 player
5 # Manage a sub process.
7 # (c) Simeon Veldstra 2006 <reallifesim@gmail.com>
9 # This software is free.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You may redistribute this program under the terms of the
17 # GNU General Public Licence version 2
18 # Available in this package or at http://www.fsf.org
31 import mp3togo.conf as setup
43 """Run a callable and save its output"""
45 def __init__(self, parent, action, fltr=None, reverse=None, tmpsize=0, outsize=0, name=''):
48 if str(self.__class__).endswith('SimpleTask'):
49 if not callable(action):
51 for func in (fltr, reverse):
53 if not callable(func):
57 self.tmpsize = tmpsize
58 self.outsize = outsize
62 self._reverse = reverse
63 # Hold runlock while accessing self._status
64 self._runlock = threading.Lock()
65 # Hold outlock while accessing self._output
66 self._outlock = threading.Lock()
73 """Run the callable"""
75 self._runlock.acquire()
76 if self._status != READY:
77 self._runlock.release()
79 self._status = RUNNING
80 self._start_time = time.time()
85 out = self._filter(out)
86 self._outlock.acquire()
88 self._outlock.release()
90 # if verbosity is high enough, print something about the exception
92 self._runlock.release()
95 self._finish_time = time.time()
96 self._runlock.release()
106 """Undo the action, if possible.
108 Runs the reverse function passed in.
109 reverse should return a True value on success or
110 False in the event of a failure. undo returns None
111 if no reverse function was passed in.
113 If reverse succeeds, the task object should be in a
114 consistent READY state and run() can start again at the
115 top. If it fails or throws an exception, the state will
119 self._runlock.acquire()
120 if self._status not in (DONE, FAILED, STOPPED):
121 self._runlock.release()
122 raise setup.TaskNotReadyError
126 ret = self._reverse()
128 self._runlock.release()
132 self._runlock.release()
136 self._runlock.acquire()
138 self._runlock.release()
144 def wait_unpause(self):
148 return (self.tmpsize, self.outsize)
151 self._outlock.acquire()
153 self._outlock.release()
156 def elapsed_time(self):
157 if not self._finish_time:
159 return self._finish_time - self._start_time
161 def check_sibling(self):
163 if bro and bro.status() != DONE:
164 raise setup.TaskNotReadyError
167 """Return the next task in the queue."""
169 queue = self._parent.tasks()
172 qi = list(queue).index(self)
173 if qi + 1 < len(queue):
177 """Return the previous task in the queue."""
179 queue = self._parent.tasks()
182 qi = list(queue).index(self)
187 class Task(SimpleTask):
188 """Run a command in a subprocess and monitor its progress"""
190 def __init__(self, parent, action, filter=None, reverse=None, tmpsize=0, outsize=0, name=''):
191 if type(action) not in (list, tuple):
193 for element in action:
194 if type(element) not in types.StringTypes:
196 self._paused = threading.Lock()
199 SimpleTask.__init__(self, parent, action, filter, reverse, tmpsize, outsize, name)
202 """Run the command in a sub process
204 This will probably only work on Linux.
209 self._runlock.acquire()
210 self._start_time = time.time()
211 if self._status == PAUSED:
212 self._paused.release()
213 self._status = RUNNING
214 self._runlock.release()
216 if self._status != READY:
217 self._runlock.release()
219 self._status = RUNNING
226 while self._status in (RUNNING, PAUSED):
227 wpid, status = os.waitpid(self._pid, os.WNOHANG)
230 self._runlock.acquire()
231 if self._paused.locked():
232 self._paused.release()
233 if os.WIFSIGNALED(status):
234 #if os.WSTOPSIG(status) == 0:
235 # self._status = DONE
237 self._status = FAILED
238 elif os.WEXITSTATUS(status) == 0:
241 self._status = FAILED
242 self._runlock.release()
244 if self._paused.locked():
246 rfd, wfd, xfd = select.select([master_fd], [], [], 5)
256 out = self._filter(out)
259 self._outlock.acquire()
261 self._outlock.release()
262 # Child still running, no more output: block on child's exit
263 if self._status in (RUNNING, PAUSED):
264 self._runlock.acquire()
265 if self._paused.locked():
266 self._paused.release()
268 wpid, status = os.waitpid(pid, 0)
269 if os.WIFSIGNALED(status):
270 if os.WTERMSIG(status) == signal.SIGHUP:
271 self._status = FAILED
273 self._status = FAILED
274 elif os.WEXITSTATUS(status) == 0:
277 self._status = FAILED
279 self._status = FAILED
280 self._runlock.release()
281 self._finish_time = time.time()
283 self._eater = threading.Thread(None, eatout)
286 pid, master_fd = pty.fork()
290 os.execvp(self._action[0], self._action)
294 fp = os.fdopen(master_fd, 'r', 0)
295 if os.uname()[0] == "Linux":
296 fcntl.fcntl(master_fd, fcntl.F_SETFL, os.O_NONBLOCK)
298 self._runlock.release()
302 self._runlock.acquire()
303 if self._status == RUNNING:
304 self._paused.acquire()
305 self._status = PAUSED
306 self._runlock.release()
309 self._runlock.release()
313 """Stop the running process"""
314 self._runlock.acquire()
315 if self._status == RUNNING:
317 os.kill(self._pid, signal.SIGKILL)
318 self._status = STOPPED
319 self._runlock.release()
324 self._runlock.release()
331 def wait_unpause(self):
332 self._paused.acquire()
333 self._paused.release()