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()
91 self._runlock.release()
94 self._finish_time = time.time()
95 self._runlock.release()
105 """Undo the action, if possible.
107 Runs the reverse function passed in.
108 reverse should return a True value on success or
109 False in the event of a failure. undo returns None
110 if no reverse function was passed in.
112 If reverse succeeds, the task object should be in a
113 consistent READY state and run() can start again at the
114 top. If it fails or throws an exception, the state will
118 self._runlock.acquire()
119 if self._status not in (DONE, FAILED, STOPPED):
120 self._runlock.release()
121 raise setup.TaskNotReadyError
125 ret = self._reverse()
127 self._runlock.release()
131 self._runlock.release()
135 self._runlock.acquire()
137 self._runlock.release()
143 def wait_unpause(self):
147 return (self.tmpsize, self.outsize)
150 self._outlock.acquire()
152 self._outlock.release()
155 def elapsed_time(self):
156 if not self._finish_time:
158 return self._finish_time - self._start_time
160 def check_sibling(self):
162 if bro and bro.status() != DONE:
163 raise setup.TaskNotReadyError
166 """Return the next task in the queue."""
168 queue = self._parent.tasks()
171 qi = list(queue).index(self)
172 if qi + 1 < len(queue):
176 """Return the previous task in the queue."""
178 queue = self._parent.tasks()
181 qi = list(queue).index(self)
186 class Task(SimpleTask):
187 """Run a command in a subprocess and monitor its progress"""
189 def __init__(self, parent, action, filter=None, reverse=None, tmpsize=0, outsize=0, name=''):
190 if type(action) not in (list, tuple):
192 for element in action:
193 if type(element) not in types.StringTypes:
195 self._paused = threading.Lock()
198 SimpleTask.__init__(self, parent, action, filter, reverse, tmpsize, outsize, name)
201 """Run the command in a sub process
203 This will probably only work on Linux.
208 self._runlock.acquire()
209 self._start_time = time.time()
210 if self._status == PAUSED:
211 self._paused.release()
212 self._status = RUNNING
213 self._runlock.release()
215 if self._status != READY:
216 self._runlock.release()
218 self._status = RUNNING
225 while self._status in (RUNNING, PAUSED):
226 wpid, status = os.waitpid(pid, os.WNOHANG)
229 self._runlock.acquire()
230 if self._paused.locked():
231 self._paused.release()
232 if os.WIFSIGNALED(status):
233 #if os.WSTOPSIG(status) == 0:
234 # self._status = DONE
236 self._status = FAILED
237 elif os.WEXITSTATUS(status) == 0:
240 self._status = FAILED
241 self._runlock.release()
243 if self._paused.locked():
245 rfd, wfd, xfd = select.select([master_fd], [], [], 5)
255 out = self._filter(out)
258 self._outlock.acquire()
260 self._outlock.release()
261 # Child still running, no more output: block on child's exit
262 if self._status in (RUNNING, PAUSED):
263 self._runlock.acquire()
264 if self._paused.locked():
265 self._paused.release()
267 wpid, status = os.waitpid(pid, 0)
268 if os.WIFSIGNALED(status):
269 if os.WTERMSIG(status) == signal.SIGHUP:
270 self._status = FAILED
272 self._status = FAILED
273 elif os.WEXITSTATUS(status) == 0:
276 self._status = FAILED
278 self._status = FAILED
279 self._runlock.release()
280 self._finish_time = time.time()
282 self._eater = threading.Thread(None, eatout)
285 pid, master_fd = pty.fork()
289 os.execvp(self._action[0], self._action)
293 fp = os.fdopen(master_fd, 'r', 0)
294 if os.uname()[0] == "Linux":
295 fcntl.fcntl(master_fd, fcntl.F_SETFL, os.O_NONBLOCK)
297 self._runlock.release()
301 self._runlock.acquire()
302 if self._status == RUNNING:
303 self._paused.acquire()
304 self._status = PAUSED
305 self._runlock.release()
308 self._runlock.release()
312 """Stop the running process"""
313 self._runlock.acquire()
314 if self._status == RUNNING:
316 os.kill(self._pid, signal.SIGKILL)
317 self._status = STOPPED
318 self._runlock.release()
323 self._runlock.release()
330 def wait_unpause(self):
331 self._paused.acquire()
332 self._paused.release()