mirror of
https://github.com/gryf/ebook-converter.git
synced 2025-12-28 12:12:26 +01:00
Here is the first batch of modules, which are needed for converting several formats to LRF. Some of the logic has been change, more cleanups will follow.
238 lines
8.4 KiB
Python
238 lines
8.4 KiB
Python
#!/usr/bin/env python2
|
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
|
|
__license__ = 'GPL v3'
|
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
|
__docformat__ = 'restructuredtext en'
|
|
|
|
import subprocess, os, sys, time
|
|
from functools import partial
|
|
|
|
from ebook_converter.constants import iswindows, isosx, isfrozen, filesystem_encoding, ispy3
|
|
from ebook_converter.utils.config import prefs
|
|
from ebook_converter.ptempfile import PersistentTemporaryFile, base_dir
|
|
from ebook_converter.utils.serialize import msgpack_dumps
|
|
from ebook_converter.polyglot.builtins import iteritems, unicode_type, string_or_bytes, environ_item, native_string_type, getcwd
|
|
from ebook_converter.polyglot.binary import as_hex_unicode
|
|
|
|
if iswindows:
|
|
import win32process
|
|
try:
|
|
windows_null_file = open(os.devnull, 'wb')
|
|
except:
|
|
raise RuntimeError('NUL file missing in windows. This indicates a'
|
|
' corrupted windows. You should contact Microsoft'
|
|
' for assistance and/or follow the steps described here: https://bytes.com/topic/net/answers/264804-compile-error-null-device-missing')
|
|
|
|
|
|
def renice(niceness):
|
|
try:
|
|
os.nice(niceness)
|
|
except:
|
|
pass
|
|
|
|
|
|
class Worker(object):
|
|
'''
|
|
Platform independent object for launching child processes. All processes
|
|
have the environment variable :envvar:`CALIBRE_WORKER` set.
|
|
|
|
Useful attributes: ``is_alive``, ``returncode``, ``pid``
|
|
Useful methods: ``kill``
|
|
|
|
To launch child simply call the Worker object. By default, the child's
|
|
output is redirected to an on disk file, the path to which is returned by
|
|
the call.
|
|
'''
|
|
|
|
exe_name = 'calibre-parallel'
|
|
|
|
@property
|
|
def executable(self):
|
|
if hasattr(sys, 'running_from_setup'):
|
|
return [sys.executable, os.path.join(sys.setup_dir, 'run-calibre-worker.py')]
|
|
if getattr(sys, 'run_local', False):
|
|
return [sys.executable, sys.run_local, self.exe_name]
|
|
e = self.exe_name
|
|
if iswindows:
|
|
return os.path.join(os.path.dirname(sys.executable),
|
|
e+'.exe' if isfrozen else 'Scripts\\%s.exe'%e)
|
|
if isosx:
|
|
return os.path.join(sys.binaries_path, e)
|
|
|
|
if isfrozen:
|
|
return os.path.join(sys.executables_location, e)
|
|
|
|
if hasattr(sys, 'executables_location'):
|
|
c = os.path.join(sys.executables_location, e)
|
|
if os.access(c, os.X_OK):
|
|
return c
|
|
return e
|
|
|
|
@property
|
|
def gui_executable(self):
|
|
if isosx and not hasattr(sys, 'running_from_setup'):
|
|
if self.job_name == 'ebook-viewer':
|
|
base = os.path.dirname(sys.binaries_path)
|
|
return os.path.join(base, 'ebook-viewer.app/Contents/MacOS/', self.exe_name)
|
|
if self.job_name == 'ebook-edit':
|
|
base = os.path.dirname(sys.binaries_path)
|
|
return os.path.join(base, 'ebook-viewer.app/Contents/ebook-edit.app/Contents/MacOS/', self.exe_name)
|
|
|
|
return os.path.join(sys.binaries_path, self.exe_name)
|
|
|
|
return self.executable
|
|
|
|
@property
|
|
def env(self):
|
|
if ispy3:
|
|
env = os.environ.copy()
|
|
else:
|
|
# We use this inefficient method of copying the environment variables
|
|
# because of non ascii env vars on windows. See https://bugs.launchpad.net/bugs/811191
|
|
env = {}
|
|
for key in os.environ:
|
|
try:
|
|
val = os.environ[key]
|
|
if isinstance(val, unicode_type):
|
|
# On windows subprocess cannot handle unicode env vars
|
|
try:
|
|
val = val.encode(filesystem_encoding)
|
|
except ValueError:
|
|
val = val.encode('utf-8')
|
|
if isinstance(key, unicode_type):
|
|
key = key.encode('ascii')
|
|
env[key] = val
|
|
except:
|
|
pass
|
|
env[native_string_type('CALIBRE_WORKER')] = environ_item('1')
|
|
td = as_hex_unicode(msgpack_dumps(base_dir()))
|
|
env[native_string_type('CALIBRE_WORKER_TEMP_DIR')] = environ_item(td)
|
|
env.update(self._env)
|
|
return env
|
|
|
|
@property
|
|
def is_alive(self):
|
|
return hasattr(self, 'child') and self.child.poll() is None
|
|
|
|
@property
|
|
def returncode(self):
|
|
if not hasattr(self, 'child'):
|
|
return None
|
|
self.child.poll()
|
|
return self.child.returncode
|
|
|
|
@property
|
|
def pid(self):
|
|
if not hasattr(self, 'child'):
|
|
return None
|
|
return getattr(self.child, 'pid', None)
|
|
|
|
def close_log_file(self):
|
|
try:
|
|
self._file.close()
|
|
except:
|
|
pass
|
|
|
|
def kill(self):
|
|
self.close_log_file()
|
|
try:
|
|
if self.is_alive:
|
|
if iswindows:
|
|
return self.child.kill()
|
|
try:
|
|
self.child.terminate()
|
|
st = time.time()
|
|
while self.is_alive and time.time()-st < 2:
|
|
time.sleep(0.2)
|
|
finally:
|
|
if self.is_alive:
|
|
self.child.kill()
|
|
except:
|
|
pass
|
|
|
|
def __init__(self, env, gui=False, job_name=None):
|
|
self._env = {}
|
|
self.gui = gui
|
|
self.job_name = job_name
|
|
if ispy3:
|
|
self._env = env.copy()
|
|
else:
|
|
# Windows cannot handle unicode env vars
|
|
for k, v in iteritems(env):
|
|
try:
|
|
if isinstance(k, unicode_type):
|
|
k = k.encode('ascii')
|
|
if isinstance(v, unicode_type):
|
|
try:
|
|
v = v.encode(filesystem_encoding)
|
|
except:
|
|
v = v.encode('utf-8')
|
|
self._env[k] = v
|
|
except:
|
|
pass
|
|
|
|
def __call__(self, redirect_output=True, cwd=None, priority=None):
|
|
'''
|
|
If redirect_output is True, output from the child is redirected
|
|
to a file on disk and this method returns the path to that file.
|
|
'''
|
|
exe = self.gui_executable if self.gui else self.executable
|
|
env = self.env
|
|
try:
|
|
origwd = cwd or os.path.abspath(getcwd())
|
|
except EnvironmentError:
|
|
# cwd no longer exists
|
|
origwd = cwd or os.path.expanduser('~')
|
|
env[native_string_type('ORIGWD')] = environ_item(as_hex_unicode(msgpack_dumps(origwd)))
|
|
_cwd = cwd
|
|
if priority is None:
|
|
priority = prefs['worker_process_priority']
|
|
cmd = [exe] if isinstance(exe, string_or_bytes) else exe
|
|
args = {
|
|
'env' : env,
|
|
'cwd' : _cwd,
|
|
}
|
|
if iswindows:
|
|
priority = {
|
|
'high' : win32process.HIGH_PRIORITY_CLASS,
|
|
'normal' : win32process.NORMAL_PRIORITY_CLASS,
|
|
'low' : win32process.IDLE_PRIORITY_CLASS}[priority]
|
|
args['creationflags'] = win32process.CREATE_NO_WINDOW|priority
|
|
else:
|
|
niceness = {
|
|
'normal' : 0,
|
|
'low' : 10,
|
|
'high' : 20,
|
|
}[priority]
|
|
args['preexec_fn'] = partial(renice, niceness)
|
|
ret = None
|
|
if redirect_output:
|
|
self._file = PersistentTemporaryFile('_worker_redirect.log')
|
|
args['stdout'] = self._file._fd
|
|
args['stderr'] = subprocess.STDOUT
|
|
if iswindows:
|
|
args['stdin'] = subprocess.PIPE
|
|
ret = self._file.name
|
|
|
|
if iswindows and 'stdin' not in args:
|
|
# On windows when using the pythonw interpreter,
|
|
# stdout, stderr and stdin may not be valid
|
|
args['stdin'] = subprocess.PIPE
|
|
args['stdout'] = windows_null_file
|
|
args['stderr'] = subprocess.STDOUT
|
|
|
|
if not iswindows:
|
|
# Close inherited file descriptors in worker
|
|
# On windows, this is done in the worker process
|
|
# itself
|
|
args['close_fds'] = True
|
|
|
|
self.child = subprocess.Popen(cmd, **args)
|
|
if 'stdin' in args:
|
|
self.child.stdin.close()
|
|
|
|
self.log_path = ret
|
|
return ret
|