mirror of
https://github.com/gryf/ebook-converter.git
synced 2025-12-28 20:42:27 +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.
301 lines
9.8 KiB
Python
301 lines
9.8 KiB
Python
__license__ = 'GPL v3'
|
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
|
__docformat__ = 'restructuredtext en'
|
|
|
|
'''
|
|
Perform various initialization tasks.
|
|
'''
|
|
|
|
import locale, sys, os
|
|
|
|
# Default translation is NOOP
|
|
from ebook_converter.polyglot.builtins import builtins, is_py3, unicode_type
|
|
builtins.__dict__['_'] = lambda s: s
|
|
|
|
# For strings which belong in the translation tables, but which shouldn't be
|
|
# immediately translated to the environment language
|
|
builtins.__dict__['__'] = lambda s: s
|
|
|
|
# For backwards compat with some third party plugins
|
|
builtins.__dict__['dynamic_property'] = lambda func: func(None)
|
|
|
|
|
|
from ebook_converter.constants import iswindows, preferred_encoding, plugins, isosx, islinux, isfrozen, DEBUG, isfreebsd, ispy3
|
|
|
|
_run_once = False
|
|
winutil = winutilerror = None
|
|
|
|
if not _run_once:
|
|
_run_once = True
|
|
from importlib import import_module
|
|
|
|
if not isfrozen and not ispy3:
|
|
# Prevent PyQt4 from being loaded
|
|
class PyQt4Ban(object):
|
|
|
|
def find_module(self, fullname, path=None):
|
|
if fullname.startswith('PyQt4'):
|
|
return self
|
|
|
|
def load_module(self, fullname):
|
|
raise ImportError('Importing PyQt4 is not allowed as calibre uses PyQt5')
|
|
|
|
sys.meta_path.insert(0, PyQt4Ban())
|
|
|
|
class DeVendor(object):
|
|
|
|
if ispy3:
|
|
|
|
def find_spec(self, fullname, path, target=None):
|
|
spec = None
|
|
if fullname == 'calibre.web.feeds.feedparser':
|
|
m = import_module('feedparser')
|
|
spec = m.__spec__
|
|
elif fullname.startswith('calibre.ebooks.markdown'):
|
|
m = import_module(fullname[len('calibre.ebooks.'):])
|
|
spec = m.__spec__
|
|
return spec
|
|
|
|
else:
|
|
|
|
def find_module(self, fullname, path=None):
|
|
if fullname == 'calibre.web.feeds.feedparser' or fullname.startswith('calibre.ebooks.markdown'):
|
|
return self
|
|
|
|
def load_module(self, fullname):
|
|
if fullname == 'calibre.web.feeds.feedparser':
|
|
return import_module('feedparser')
|
|
return import_module(fullname[len('calibre.ebooks.'):])
|
|
|
|
sys.meta_path.insert(0, DeVendor())
|
|
|
|
#
|
|
# Platform specific modules
|
|
if iswindows:
|
|
winutil, winutilerror = plugins['winutil']
|
|
if not winutil:
|
|
raise RuntimeError('Failed to load the winutil plugin: %s'%winutilerror)
|
|
if len(sys.argv) > 1 and not isinstance(sys.argv[1], unicode_type):
|
|
sys.argv[1:] = winutil.argv()[1-len(sys.argv):]
|
|
|
|
if not ispy3:
|
|
# Python2's expanduser is broken for non-ASCII usernames
|
|
# and unicode paths
|
|
|
|
def expanduser(path):
|
|
if isinstance(path, bytes):
|
|
path = path.decode('mbcs')
|
|
if path[:1] != '~':
|
|
return path
|
|
i, n = 1, len(path)
|
|
while i < n and path[i] not in '/\\':
|
|
i += 1
|
|
userhome = winutil.special_folder_path(winutil.CSIDL_PROFILE)
|
|
if i != 1: # ~user
|
|
userhome = os.path.join(os.path.dirname(userhome), path[1:i])
|
|
|
|
return userhome + path[i:]
|
|
os.path.expanduser = expanduser
|
|
|
|
# Ensure that all temp files/dirs are created under a calibre tmp dir
|
|
from ebook_converter.ptempfile import base_dir
|
|
try:
|
|
base_dir()
|
|
except EnvironmentError:
|
|
pass # Ignore this error during startup, so we can show a better error message to the user later.
|
|
|
|
#
|
|
# Convert command line arguments to unicode
|
|
enc = preferred_encoding
|
|
if isosx:
|
|
enc = 'utf-8'
|
|
for i in range(1, len(sys.argv)):
|
|
if not isinstance(sys.argv[i], unicode_type):
|
|
sys.argv[i] = sys.argv[i].decode(enc, 'replace')
|
|
|
|
#
|
|
# Ensure that the max number of open files is at least 1024
|
|
if iswindows:
|
|
# See https://msdn.microsoft.com/en-us/library/6e3b887c.aspx
|
|
if hasattr(winutil, 'setmaxstdio'):
|
|
winutil.setmaxstdio(max(1024, winutil.getmaxstdio()))
|
|
else:
|
|
import resource
|
|
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
if soft < 1024:
|
|
try:
|
|
resource.setrlimit(resource.RLIMIT_NOFILE, (min(1024, hard), hard))
|
|
except Exception:
|
|
if DEBUG:
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
#
|
|
# Setup resources
|
|
import ebook_converter.utils.resources as resources
|
|
resources
|
|
|
|
#
|
|
# Setup translations
|
|
from ebook_converter.utils.localization import set_translators
|
|
|
|
set_translators()
|
|
|
|
#
|
|
# Initialize locale
|
|
# Import string as we do not want locale specific
|
|
# string.whitespace/printable, on windows especially, this causes problems.
|
|
# Before the delay load optimizations, string was loaded before this point
|
|
# anyway, so we preserve the old behavior explicitly.
|
|
import string
|
|
string
|
|
try:
|
|
locale.setlocale(locale.LC_ALL, '') # set the locale to the user's default locale
|
|
except:
|
|
dl = locale.getdefaultlocale()
|
|
try:
|
|
if dl:
|
|
locale.setlocale(locale.LC_ALL, dl[0])
|
|
except:
|
|
pass
|
|
|
|
# local_open() opens a file that wont be inherited by child processes
|
|
if is_py3:
|
|
local_open = open # PEP 446
|
|
elif iswindows:
|
|
def local_open(name, mode='r', bufsize=-1):
|
|
mode += 'N'
|
|
return open(name, mode, bufsize)
|
|
elif isosx:
|
|
import fcntl
|
|
FIOCLEX = 0x20006601
|
|
|
|
def local_open(name, mode='r', bufsize=-1):
|
|
ans = open(name, mode, bufsize)
|
|
try:
|
|
fcntl.ioctl(ans.fileno(), FIOCLEX)
|
|
except EnvironmentError:
|
|
fcntl.fcntl(ans, fcntl.F_SETFD, fcntl.fcntl(ans, fcntl.F_GETFD) | fcntl.FD_CLOEXEC)
|
|
return ans
|
|
else:
|
|
import fcntl
|
|
try:
|
|
cloexec_flag = fcntl.FD_CLOEXEC
|
|
except AttributeError:
|
|
cloexec_flag = 1
|
|
supports_mode_e = False
|
|
|
|
def local_open(name, mode='r', bufsize=-1):
|
|
global supports_mode_e
|
|
mode += 'e'
|
|
ans = open(name, mode, bufsize)
|
|
if supports_mode_e:
|
|
return ans
|
|
old = fcntl.fcntl(ans, fcntl.F_GETFD)
|
|
if not (old & cloexec_flag):
|
|
fcntl.fcntl(ans, fcntl.F_SETFD, old | cloexec_flag)
|
|
else:
|
|
supports_mode_e = True
|
|
return ans
|
|
|
|
builtins.__dict__['lopen'] = local_open
|
|
|
|
from ebook_converter.utils.icu import title_case, lower as icu_lower, upper as icu_upper
|
|
builtins.__dict__['icu_lower'] = icu_lower
|
|
builtins.__dict__['icu_upper'] = icu_upper
|
|
builtins.__dict__['icu_title'] = title_case
|
|
|
|
def connect_lambda(bound_signal, self, func, **kw):
|
|
import weakref
|
|
r = weakref.ref(self)
|
|
del self
|
|
num_args = func.__code__.co_argcount - 1
|
|
if num_args < 0:
|
|
raise TypeError('lambda must take at least one argument')
|
|
|
|
def slot(*args):
|
|
ctx = r()
|
|
if ctx is not None:
|
|
if len(args) != num_args:
|
|
args = args[:num_args]
|
|
func(ctx, *args)
|
|
|
|
bound_signal.connect(slot, **kw)
|
|
builtins.__dict__['connect_lambda'] = connect_lambda
|
|
|
|
if islinux or isosx or isfreebsd:
|
|
# Name all threads at the OS level created using the threading module, see
|
|
# http://bugs.python.org/issue15500
|
|
import threading
|
|
|
|
orig_start = threading.Thread.start
|
|
|
|
def new_start(self):
|
|
orig_start(self)
|
|
try:
|
|
name = self.name
|
|
if not name or name.startswith('Thread-'):
|
|
name = self.__class__.__name__
|
|
if name == 'Thread':
|
|
name = self.name
|
|
if name:
|
|
if isinstance(name, unicode_type):
|
|
name = name.encode('ascii', 'replace').decode('ascii')
|
|
plugins['speedup'][0].set_thread_name(name[:15])
|
|
except Exception:
|
|
pass # Don't care about failure to set name
|
|
threading.Thread.start = new_start
|
|
|
|
|
|
def test_lopen():
|
|
from ebook_converter.ptempfile import TemporaryDirectory
|
|
from ebook_converter import CurrentDir
|
|
n = 'f\xe4llen'
|
|
print('testing lopen()')
|
|
|
|
if iswindows:
|
|
import msvcrt, win32api
|
|
|
|
def assert_not_inheritable(f):
|
|
if win32api.GetHandleInformation(msvcrt.get_osfhandle(f.fileno())) & 0b1:
|
|
raise SystemExit('File handle is inheritable!')
|
|
else:
|
|
import fcntl
|
|
|
|
def assert_not_inheritable(f):
|
|
if not fcntl.fcntl(f, fcntl.F_GETFD) & fcntl.FD_CLOEXEC:
|
|
raise SystemExit('File handle is inheritable!')
|
|
|
|
def copen(*args):
|
|
ans = lopen(*args)
|
|
assert_not_inheritable(ans)
|
|
return ans
|
|
|
|
with TemporaryDirectory() as tdir, CurrentDir(tdir):
|
|
with copen(n, 'w') as f:
|
|
f.write('one')
|
|
|
|
print('O_CREAT tested')
|
|
with copen(n, 'w+b') as f:
|
|
f.write(b'two')
|
|
with copen(n, 'r') as f:
|
|
if f.read() == 'two':
|
|
print('O_TRUNC tested')
|
|
else:
|
|
raise Exception('O_TRUNC failed')
|
|
with copen(n, 'ab') as f:
|
|
f.write(b'three')
|
|
with copen(n, 'r+') as f:
|
|
if f.read() == 'twothree':
|
|
print('O_APPEND tested')
|
|
else:
|
|
raise Exception('O_APPEND failed')
|
|
with copen(n, 'r+') as f:
|
|
f.seek(3)
|
|
f.write('xxxxx')
|
|
f.seek(0)
|
|
if f.read() == 'twoxxxxx':
|
|
print('O_RANDOM tested')
|
|
else:
|
|
raise Exception('O_RANDOM failed')
|