mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Allow jobs to be killed from the GUI
This commit is contained in:
parent
ff46adc03e
commit
76840c13e4
@ -31,7 +31,17 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
|||||||
self.setWindowTitle(__appname__ + ' - Active Jobs')
|
self.setWindowTitle(__appname__ + ' - Active Jobs')
|
||||||
QObject.connect(self.jobs_view.model(), SIGNAL('modelReset()'),
|
QObject.connect(self.jobs_view.model(), SIGNAL('modelReset()'),
|
||||||
self.jobs_view.resizeColumnsToContents)
|
self.jobs_view.resizeColumnsToContents)
|
||||||
|
QObject.connect(self.kill_button, SIGNAL('clicked()'),
|
||||||
|
self.kill_job)
|
||||||
|
QObject.connect(self, SIGNAL('kill_job(int, PyQt_PyObject)'),
|
||||||
|
self.jobs_view.model().kill_job)
|
||||||
|
|
||||||
|
def kill_job(self):
|
||||||
|
for index in self.jobs_view.selectedIndexes():
|
||||||
|
row = index.row()
|
||||||
|
self.emit(SIGNAL('kill_job(int, PyQt_PyObject)'), row, self)
|
||||||
|
return
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
self.jobs_view.write_settings()
|
self.jobs_view.write_settings()
|
||||||
e.accept()
|
e.accept()
|
||||||
|
@ -41,6 +41,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="kill_button" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>&Stop selected job</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
## You should have received a copy of the GNU General Public License along
|
## You should have received a copy of the GNU General Public License along
|
||||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
from libprs500.gui2 import error_dialog
|
||||||
import traceback, logging, collections
|
import traceback, logging, collections
|
||||||
|
|
||||||
from PyQt4.QtCore import QAbstractTableModel, QMutex, QObject, SIGNAL, Qt, \
|
from PyQt4.QtCore import QAbstractTableModel, QMutex, QObject, SIGNAL, Qt, \
|
||||||
@ -170,7 +171,8 @@ class JobManager(QAbstractTableModel):
|
|||||||
for job in [job for job in self.running_jobs if job.isFinished()]:
|
for job in [job for job in self.running_jobs if job.isFinished()]:
|
||||||
self.running_jobs.remove(job)
|
self.running_jobs.remove(job)
|
||||||
self.finished_jobs.appendleft(job)
|
self.finished_jobs.appendleft(job)
|
||||||
job.notify()
|
if job.result != self.process_server.KILL_RESULT:
|
||||||
|
job.notify()
|
||||||
self.emit(SIGNAL('job_done(int)'), job.id)
|
self.emit(SIGNAL('job_done(int)'), job.id)
|
||||||
refresh = True
|
refresh = True
|
||||||
|
|
||||||
@ -317,7 +319,9 @@ class JobManager(QAbstractTableModel):
|
|||||||
if status == 0:
|
if status == 0:
|
||||||
return self.running_icon
|
return self.running_icon
|
||||||
if status == 2:
|
if status == 2:
|
||||||
return self.done_icon if job.exception is None else self.error_icon
|
if job.exception or job.result == self.process_server.KILL_RESULT:
|
||||||
|
return self.error_icon
|
||||||
|
return self.done_icon
|
||||||
return NONE
|
return NONE
|
||||||
|
|
||||||
def status_update(self, id, progress):
|
def status_update(self, id, progress):
|
||||||
@ -327,6 +331,22 @@ class JobManager(QAbstractTableModel):
|
|||||||
index = self.index(i, 2)
|
index = self.index(i, 2)
|
||||||
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), index, index)
|
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), index, index)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def kill_job(self, row, gui_parent):
|
||||||
|
job, status = self.row_to_job(row)
|
||||||
|
if isinstance(job, DeviceJob):
|
||||||
|
error_dialog(gui_parent, _('Cannot kill job'),
|
||||||
|
_('Cannot kill jobs that are communicating with the device as this may cause data corruption.')).exec_()
|
||||||
|
return
|
||||||
|
if status == 2:
|
||||||
|
error_dialog(gui_parent, _('Cannot kill job'),
|
||||||
|
_('Cannot kill already completed jobs.')).exec_()
|
||||||
|
return
|
||||||
|
if status == 1:
|
||||||
|
error_dialog(gui_parent, _('Cannot kill job'),
|
||||||
|
_('Cannot kill waiting jobs.')).exec_()
|
||||||
|
return
|
||||||
|
self.process_server.kill(job.id)
|
||||||
|
|
||||||
class DetailView(QDialog, Ui_Dialog):
|
class DetailView(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ from functools import partial
|
|||||||
from libprs500.ebooks.lrf.any.convert_from import main as any2lrf
|
from libprs500.ebooks.lrf.any.convert_from import main as any2lrf
|
||||||
from libprs500.ebooks.lrf.web.convert_from import main as web2lrf
|
from libprs500.ebooks.lrf.web.convert_from import main as web2lrf
|
||||||
from libprs500.gui2.lrf_renderer.main import main as lrfviewer
|
from libprs500.gui2.lrf_renderer.main import main as lrfviewer
|
||||||
from libprs500 import iswindows
|
from libprs500 import iswindows, __appname__
|
||||||
|
|
||||||
PARALLEL_FUNCS = {
|
PARALLEL_FUNCS = {
|
||||||
'any2lrf' : partial(any2lrf, gui_mode=True),
|
'any2lrf' : partial(any2lrf, gui_mode=True),
|
||||||
@ -47,11 +47,44 @@ def cleanup(tdir):
|
|||||||
|
|
||||||
class Server(object):
|
class Server(object):
|
||||||
|
|
||||||
|
#: Interval in seconds at which child processes are polled for status information
|
||||||
|
INTERVAL = 0.1
|
||||||
|
KILL_RESULT = 'Server: job killed by user|||#@#$%&*)*(*$#$%#$@&'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.tdir = tempfile.mkdtemp('', 'libprs500_IPC_')
|
self.tdir = tempfile.mkdtemp('', '%s_IPC_'%__appname__)
|
||||||
atexit.register(cleanup, self.tdir)
|
atexit.register(cleanup, self.tdir)
|
||||||
self.stdout = {}
|
self.stdout = {}
|
||||||
|
self.kill_jobs = []
|
||||||
|
|
||||||
|
def kill(self, job_id):
|
||||||
|
'''
|
||||||
|
Kill the job identified by job_id.
|
||||||
|
'''
|
||||||
|
self.kill_jobs.append(str(job_id))
|
||||||
|
|
||||||
|
def _terminate(self, pid):
|
||||||
|
'''
|
||||||
|
Kill process identified by C{pid}.
|
||||||
|
@param pid: On unix a process number, on windows a process handle.
|
||||||
|
'''
|
||||||
|
if iswindows:
|
||||||
|
import win32api
|
||||||
|
try:
|
||||||
|
win32api.TerminateProcess(int(pid), -1)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
import signal
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
os.kill(pid, signal.SIGTERM)
|
||||||
|
finally:
|
||||||
|
time.sleep(2)
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def run(self, job_id, func, args=[], kwdargs={}, monitor=True):
|
def run(self, job_id, func, args=[], kwdargs={}, monitor=True):
|
||||||
'''
|
'''
|
||||||
Run a job in a separate process.
|
Run a job in a separate process.
|
||||||
@ -61,7 +94,8 @@ class Server(object):
|
|||||||
@param kwdargs: A dictionary of keyword arguments to pass to C{func}
|
@param kwdargs: A dictionary of keyword arguments to pass to C{func}
|
||||||
@param monitor: If False launch the child process and return. Do not monitor/communicate with it.
|
@param monitor: If False launch the child process and return. Do not monitor/communicate with it.
|
||||||
@return: (result, exception, formatted_traceback, log) where log is the combined
|
@return: (result, exception, formatted_traceback, log) where log is the combined
|
||||||
stdout + stderr of the child process; or None if monitor is True.
|
stdout + stderr of the child process; or None if monitor is True. If a job is killed
|
||||||
|
by a call to L{kill()} then result will be L{KILL_RESULT}
|
||||||
'''
|
'''
|
||||||
job_id = str(job_id)
|
job_id = str(job_id)
|
||||||
job_dir = os.path.join(self.tdir, job_id)
|
job_dir = os.path.join(self.tdir, job_id)
|
||||||
@ -86,7 +120,9 @@ class Server(object):
|
|||||||
Popen((python, '-c', cmd))
|
Popen((python, '-c', cmd))
|
||||||
return
|
return
|
||||||
while p.returncode is None:
|
while p.returncode is None:
|
||||||
self.stdout[job_id].write(p.stdout.readline())
|
if job_id in self.kill_jobs:
|
||||||
|
self._terminate(p._handle if iswindows else p.pid)
|
||||||
|
return self.KILL_RESULT, None, None, _('Job killed by user')
|
||||||
p.poll()
|
p.poll()
|
||||||
time.sleep(0.5) # Wait for half a second
|
time.sleep(0.5) # Wait for half a second
|
||||||
self.stdout[job_id].write(p.stdout.read())
|
self.stdout[job_id].write(p.stdout.read())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user