Make viewer run in separate process. Should fix #533

This commit is contained in:
Kovid Goyal 2008-02-24 03:14:03 +00:00
parent 6cf529b5da
commit 4ba916c130
2 changed files with 39 additions and 24 deletions

View File

@ -12,7 +12,7 @@
## 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.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.Warning
import os, sys, textwrap, cStringIO, collections, traceback, shutil
import os, sys, textwrap, collections, traceback, shutil, time
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
QSettings, QVariant, QSize, QThread, QString
@ -46,8 +46,6 @@ from libprs500.gui2.dialogs.lrf_single import LRFSingleDialog, LRFBulkDialog
from libprs500.gui2.dialogs.config import ConfigDialog
from libprs500.gui2.dialogs.search import SearchDialog
from libprs500.gui2.dialogs.user_profiles import UserProfiles
from libprs500.gui2.lrf_renderer.main import file_renderer
from libprs500.gui2.lrf_renderer.main import option_parser as lrfviewerop
from libprs500.library.database import DatabaseLocked
from libprs500.ebooks.metadata.meta import set_metadata
from libprs500.ebooks.metadata import MetaInformation
@ -80,6 +78,7 @@ class Main(MainWindow, Ui_MainWindow):
self.delete_memory = {}
self.conversion_jobs = {}
self.persistent_files = []
self.viewer_job_id = 1
self.default_thumbnail = None
self.device_error_dialog = ConversionErrorDialog(self, _('Error communicating with device'), ' ')
self.device_error_dialog.setModal(Qt.NonModal)
@ -745,23 +744,21 @@ class Main(MainWindow, Ui_MainWindow):
title = self.library_view.model().db.title(row)
id = self.library_view.model().db.id(row)
if 'LRF' not in formats.upper():
d = error_dialog(self, _('Cannot view'), _('%s is not available in LRF format. Please convert it first.')%(title,))
d = error_dialog(self, _('Cannot view'),
_('%s is not available in LRF format. Please convert it first.')%(title,))
d.exec_()
return
data = cStringIO.StringIO(self.library_view.model().db.format(row, 'LRF'))
parser = lrfviewerop()
opts = parser.parse_args(['lrfviewer'])[0]
viewer = file_renderer(data, opts)
viewer.libprs500_db_id = id
viewer.show()
viewer.render()
self.viewers.append(viewer)
QObject.connect(viewer, SIGNAL('viewer_closed(PyQt_PyObject)'), self.viewer_closed)
def viewer_closed(self, viewer):
self.viewers.remove(viewer)
pt = PersistentTemporaryFile('_viewer')
pt.write(self.library_view.model().db.format(row, 'LRF'))
pt.close()
self.persistent_files.append(pt)
args = ['lrfviewer', pt.name]
self.job_manager.process_server.run('viewer%d'%self.viewer_job_id,
'lrfviewer', kwdargs=dict(args=args),
monitor=False)
self.viewer_job_id += 1
time.sleep(2) # User feedback
############################################################################

View File

@ -19,11 +19,13 @@ import re, sys, tempfile, os, subprocess, cPickle, cStringIO, traceback, atexit,
from functools import partial
from libprs500.ebooks.lrf.any.convert_from import main as any2lrf
from libprs500.ebooks.lrf.web.convert_from import main as web2lrf
from libprs500.gui2.lrf_renderer.main import main as lrfviewer
from libprs500 import iswindows
PARALLEL_FUNCS = {
'any2lrf' : partial(any2lrf, gui_mode=True),
'web2lrf' : web2lrf,
'lrfviewer' : lrfviewer,
}
Popen = subprocess.Popen
@ -50,11 +52,21 @@ class Server(object):
atexit.register(cleanup, self.tdir)
self.stdout = {}
def run(self, job_id, func, args=(), kwdargs={}):
def run(self, job_id, func, args=[], kwdargs={}, monitor=True):
'''
Run a job in a separate process.
@param job_id: A unique (per server) identifier
@param func: One of C{PARALLEL_FUNCS.keys()}
@param args: A list of arguments to pass of 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.
@return: (result, exception, formatted_traceback, log) where log is the combined
stdout + stderr of the child process; or None if monitor is True.
'''
job_id = str(job_id)
job_dir = os.path.join(self.tdir, job_id)
if os.path.exists(job_dir):
raise ValueError('Cannot run job. The job_id %s has already been used.')
raise ValueError('Cannot run job. The job_id %s has already been used.'%job_id)
os.mkdir(job_dir)
self.stdout[job_id] = cStringIO.StringIO()
@ -68,7 +80,11 @@ class Server(object):
os.environ['PATH'] += ':'+fd
cmd = prefix + 'from libprs500.parallel import run_job; run_job(\'%s\')'%binascii.hexlify(job_data)
if monitor:
p = Popen((python, '-c', cmd), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
else:
Popen((python, '-c', cmd))
return
while p.returncode is None:
self.stdout[job_id].write(p.stdout.readline())
p.poll()
@ -77,7 +93,8 @@ class Server(object):
job_result = os.path.join(job_dir, 'job_result.pickle')
if not os.path.exists(job_result):
result, exception, traceback = None, ('ParallelRuntimeError', 'The worker process died unexpectedly.'), ''
result, exception, traceback = None, ('ParallelRuntimeError',
'The worker process died unexpectedly.'), ''
else:
result, exception, traceback = cPickle.load(open(job_result, 'rb'))
log = self.stdout[job_id].getvalue()
@ -98,6 +115,7 @@ def run_job(job_data):
exception = (err.__class__.__name__, unicode(str(err), 'utf-8', 'replace'))
tb = traceback.format_exc()
if os.path.exists(os.path.dirname(job_result)):
cPickle.dump((result, exception, tb), open(job_result, 'wb'))
def main():