%PDF- %PDF-
Direktori : /lib/calibre/calibre/ |
Current File : //lib/calibre/calibre/debug.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' ''' Embedded console for debugging. ''' import sys, os, functools from calibre.utils.config import OptionParser from calibre.constants import iswindows from calibre import prints from calibre.startup import get_debug_executable from polyglot.builtins import exec_path def run_calibre_debug(*args, **kw): import subprocess creationflags = 0 if iswindows: creationflags = subprocess.CREATE_NO_WINDOW cmd = get_debug_executable() + list(args) kw['creationflags'] = creationflags return subprocess.Popen(cmd, **kw) def option_parser(): parser = OptionParser(usage=_('''\ {0} Various command line interfaces useful for debugging calibre. With no options, this command starts an embedded Python interpreter. You can also run the main calibre GUI, the calibre E-book viewer and the calibre editor in debug mode. It also contains interfaces to various bits of calibre that do not have dedicated command line tools, such as font subsetting, the E-book diff tool and so on. You can also use %prog to run standalone scripts. To do that use it like this: {1} Everything after the -- is passed to the script. ''').format(_('%prog [options]'), '%prog myscript.py -- --option1 --option2 file1 file2 ...')) parser.add_option('-c', '--command', help=_('Run Python code.')) parser.add_option('-e', '--exec-file', help=_('Run the Python code in file.')) parser.add_option('-f', '--subset-font', action='store_true', default=False, help=_('Subset the specified font. Use -- after this option to pass option to the font subsetting program.')) parser.add_option('-d', '--debug-device-driver', default=False, action='store_true', help=_('Debug device detection')) parser.add_option('-g', '--gui', default=False, action='store_true', help=_('Run the GUI with debugging enabled. Debug output is ' 'printed to stdout and stderr.')) parser.add_option('--gui-debug', default=None, help=_('Run the GUI with a debug console, logging to the' ' specified path. For internal use only, use the -g' ' option to run the GUI in debug mode')) parser.add_option('-w', '--viewer', default=False, action='store_true', help=_('Run the E-book viewer in debug mode')) parser.add_option('--paths', default=False, action='store_true', help=_('Output the paths necessary to setup the calibre environment')) parser.add_option('--add-simple-plugin', default=None, help=_('Add a simple plugin (i.e. a plugin that consists of only a ' '.py file), by specifying the path to the py file containing the ' 'plugin code.')) parser.add_option('--reinitialize-db', default=None, help=_('Re-initialize the sqlite calibre database at the ' 'specified path. Useful to recover from db corruption.')) parser.add_option('-m', '--inspect-mobi', action='store_true', default=False, help=_('Inspect the MOBI file(s) at the specified path(s)')) parser.add_option('-t', '--edit-book', action='store_true', help=_('Launch the calibre "Edit book" tool in debug mode.')) parser.add_option('-x', '--explode-book', default=False, action='store_true', help=_('Explode the book into the specified folder.\nUsage: ' '-x file.epub output_dir\n' 'Exports the book as a collection of HTML ' 'files and metadata, which you can edit using standard HTML ' 'editing tools. Works with EPUB, AZW3, HTMLZ and DOCX files.')) parser.add_option('-i', '--implode-book', default=False, action='store_true', help=_( 'Implode a previously exploded book.\nUsage: -i output_dir file.epub\n' 'Imports the book from the files in output_dir which must have' ' been created by a previous call to --explode-book. Be sure to' ' specify the same file type as was used when exploding.')) parser.add_option('--export-all-calibre-data', default=False, action='store_true', help=_('Export all calibre data (books/settings/plugins). Normally, you will' ' be asked for the export folder and the libraries to export. You can also specify them' ' as command line arguments to skip the questions.' ' Use absolute paths for the export folder and libraries.' ' The special keyword "all" can be used to export all libraries.')) parser.add_option('--import-calibre-data', default=False, action='store_true', help=_('Import previously exported calibre data')) parser.add_option('-s', '--shutdown-running-calibre', default=False, action='store_true', help=_('Cause a running calibre instance, if any, to be' ' shutdown. Note that if there are running jobs, they ' 'will be silently aborted, so use with care.')) parser.add_option('--test-build', help=_('Test binary modules in build'), action='store_true', default=False) parser.add_option('-r', '--run-plugin', help=_( 'Run a plugin that provides a command line interface. For example:\n' 'calibre-debug -r "Add Books" -- file1 --option1\n' 'Everything after the -- will be passed to the plugin as arguments.')) parser.add_option('--diff', action='store_true', default=False, help=_( 'Run the calibre diff tool. For example:\n' 'calibre-debug --diff file1 file2')) parser.add_option('--default-programs', default=None, choices=['register', 'unregister'], help=_('(Un)register calibre from Windows Default Programs.') + ' --default-programs=(register|unregister)') parser.add_option('--fix-multiprocessing', default=False, action='store_true', help=_('For internal use')) return parser def reinit_db(dbpath): from contextlib import closing from calibre import as_unicode from calibre.ptempfile import TemporaryFile from calibre.utils.filenames import atomic_rename # We have to use sqlite3 instead of apsw as apsw has no way to discard # problematic statements import sqlite3 from calibre.library.sqlite import do_connect with TemporaryFile(suffix='_tmpdb.db', dir=os.path.dirname(dbpath)) as tmpdb: with closing(do_connect(dbpath)) as src, closing(do_connect(tmpdb)) as dest: dest.execute('create temporary table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)') dest.commit() uv = int(src.execute('PRAGMA user_version;').fetchone()[0]) dump = src.iterdump() last_restore_error = None while True: try: statement = next(dump) except StopIteration: break except sqlite3.OperationalError as e: prints('Failed to dump a line:', as_unicode(e)) if last_restore_error: prints('Failed to restore a line:', last_restore_error) last_restore_error = None try: dest.execute(statement) except sqlite3.OperationalError as e: last_restore_error = as_unicode(e) # The dump produces an extra commit at the end, so # only print this error if there are more # statements to be restored dest.execute('PRAGMA user_version=%d;'%uv) dest.commit() atomic_rename(tmpdb, dbpath) prints('Database successfully re-initialized') def debug_device_driver(): from calibre.devices import debug debug(ioreg_to_tmp=True, buf=sys.stdout) if iswindows: # no2to3 input('Press Enter to continue...') # no2to3 def add_simple_plugin(path_to_plugin): import tempfile, zipfile, shutil tdir = tempfile.mkdtemp() open(os.path.join(tdir, 'custom_plugin.py'), 'wb').write(open(path_to_plugin, 'rb').read()) odir = os.getcwd() os.chdir(tdir) zf = zipfile.ZipFile('plugin.zip', 'w') zf.write('custom_plugin.py') zf.close() from calibre.customize.ui import main main(['calibre-customize', '-a', 'plugin.zip']) os.chdir(odir) shutil.rmtree(tdir) def print_basic_debug_info(out=None): if out is None: out = sys.stdout out = functools.partial(prints, file=out) import platform from contextlib import suppress from calibre.constants import (__appname__, get_version, isportable, ismacos, isfrozen, is64bit) from calibre.utils.localization import set_translators out(__appname__, get_version(), 'Portable' if isportable else '', 'embedded-python:', isfrozen, 'is64bit:', is64bit) out(platform.platform(), platform.system(), platform.architecture()) if iswindows and not is64bit: from calibre_extensions.winutil import is_wow64_process with suppress(Exception): if is_wow64_process(): out('32bit process running on 64bit windows') out(platform.system_alias(platform.system(), platform.release(), platform.version())) out('Python', platform.python_version()) try: if iswindows: out('Windows:', platform.win32_ver()) elif ismacos: out('OSX:', platform.mac_ver()) else: out('Linux:', platform.linux_distribution()) except: pass out('Interface language:', str(set_translators.lang)) from calibre.customize.ui import has_external_plugins, initialized_plugins if has_external_plugins(): from calibre.customize import PluginInstallationType names = (f'{p.name} {p.version}' for p in initialized_plugins() if getattr(p, 'installation_type', None) is not PluginInstallationType.BUILTIN) out('Successfully initialized third party plugins:', ' && '.join(names)) def run_debug_gui(logpath): import time time.sleep(3) # Give previous GUI time to shutdown fully and release locks from calibre.constants import __appname__ prints(__appname__, _('Debug log')) print_basic_debug_info() from calibre.gui_launch import calibre calibre(['__CALIBRE_GUI_DEBUG__', logpath]) def load_user_plugins(): # Load all user defined plugins so the script can import from the # calibre_plugins namespace import calibre.customize.ui as dummy return dummy def run_script(path, args): load_user_plugins() sys.argv = [path] + args ef = os.path.abspath(path) if '/src/calibre/' not in ef.replace(os.pathsep, '/'): base = os.path.dirname(ef) sys.path.insert(0, base) g = globals() g['__name__'] = '__main__' g['__file__'] = ef exec_path(ef, g) def inspect_mobi(path): from calibre.ebooks.mobi.debug.main import inspect_mobi prints('Inspecting:', path) inspect_mobi(path) print() def main(args=sys.argv): from calibre.constants import debug opts, args = option_parser().parse_args(args) if opts.fix_multiprocessing: sys.argv = [sys.argv[0], '--multiprocessing-fork'] exec(args[-1]) return debug() if opts.gui: from calibre.gui_launch import calibre calibre(['calibre'] + args[1:]) elif opts.gui_debug is not None: run_debug_gui(opts.gui_debug) elif opts.viewer: from calibre.gui_launch import ebook_viewer ebook_viewer(['ebook-viewer'] + args[1:]) elif opts.command: sys.argv = args exec(opts.command) elif opts.debug_device_driver: debug_device_driver() elif opts.add_simple_plugin is not None: add_simple_plugin(opts.add_simple_plugin) elif opts.paths: prints('CALIBRE_RESOURCES_PATH='+sys.resources_location) prints('CALIBRE_EXTENSIONS_PATH='+sys.extensions_location) prints('CALIBRE_PYTHON_PATH='+os.pathsep.join(sys.path)) elif opts.reinitialize_db is not None: reinit_db(opts.reinitialize_db) elif opts.inspect_mobi: for path in args[1:]: inspect_mobi(path) elif opts.edit_book: from calibre.gui_launch import ebook_edit ebook_edit(['ebook-edit'] + args[1:]) elif opts.explode_book or opts.implode_book: from calibre.ebooks.tweak import explode, implode try: a1, a2 = args[1:] except Exception: raise SystemExit('Must provide exactly two arguments') f = explode if opts.explode_book else implode f(a1, a2) elif opts.test_build: from calibre.test_build import test, test_multiprocessing test_multiprocessing() test() elif opts.shutdown_running_calibre: from calibre.gui2.main import shutdown_other shutdown_other() elif opts.subset_font: from calibre.utils.fonts.sfnt.subset import main main(['subset-font'] + args[1:]) elif opts.exec_file: run_script(opts.exec_file, args[1:]) elif opts.run_plugin: from calibre.customize.ui import find_plugin plugin = find_plugin(opts.run_plugin) if plugin is None: prints(_('No plugin named %s found')%opts.run_plugin) raise SystemExit(1) plugin.cli_main([plugin.name] + args[1:]) elif opts.diff: from calibre.gui2.tweak_book.diff.main import main main(['calibre-diff'] + args[1:]) elif opts.default_programs: if not iswindows: raise SystemExit('Can only be run on Microsoft Windows') if opts.default_programs == 'register': from calibre.utils.winreg.default_programs import register as func else: from calibre.utils.winreg.default_programs import unregister as func print('Running', func.__name__, '...') func() elif opts.export_all_calibre_data: args = args[1:] from calibre.utils.exim import run_exporter run_exporter(args=args) elif opts.import_calibre_data: from calibre.utils.exim import run_importer run_importer() elif len(args) >= 2 and args[1].rpartition('.')[-1] in {'py', 'recipe'}: run_script(args[1], args[2:]) elif len(args) >= 2 and args[1].rpartition('.')[-1] in {'mobi', 'azw', 'azw3', 'docx', 'odt'}: for path in args[1:]: ext = path.rpartition('.')[-1] if ext in {'docx', 'odt'}: from calibre.ebooks.docx.dump import dump dump(path) elif ext in {'mobi', 'azw', 'azw3'}: inspect_mobi(path) else: print('Cannot dump unknown filetype: %s' % path) elif len(args) >= 2 and os.path.exists(os.path.join(args[1], '__main__.py')): sys.path.insert(0, args[1]) run_script(os.path.join(args[1], '__main__.py'), args[2:]) else: load_user_plugins() from calibre import ipython ipython() return 0 if __name__ == '__main__': sys.exit(main())