From 4ba916c130d57889a5a0d9c99d201ac60b51869e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 24 Feb 2008 03:14:03 +0000 Subject: [PATCH] Make viewer run in separate process. Should fix #533 --- src/libprs500/gui2/main.py | 31 ++++++++++++++----------------- src/libprs500/parallel.py | 32 +++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/libprs500/gui2/main.py b/src/libprs500/gui2/main.py index b829a3d7bd..d788b7ac0e 100644 --- a/src/libprs500/gui2/main.py +++ b/src/libprs500/gui2/main.py @@ -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 ############################################################################ diff --git a/src/libprs500/parallel.py b/src/libprs500/parallel.py index 1c2dd20ff3..5bf0d9636f 100644 --- a/src/libprs500/parallel.py +++ b/src/libprs500/parallel.py @@ -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, + '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) - p = Popen((python, '-c', cmd), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + 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,7 +115,8 @@ def run_job(job_data): exception = (err.__class__.__name__, unicode(str(err), 'utf-8', 'replace')) tb = traceback.format_exc() - cPickle.dump((result, exception, tb), open(job_result, 'wb')) + if os.path.exists(os.path.dirname(job_result)): + cPickle.dump((result, exception, tb), open(job_result, 'wb')) def main(): src = sys.argv[2]