From 6690187e0ab791d3a02148fbfb6f02f03546e1f0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Oct 2020 18:01:38 +0530 Subject: [PATCH] Allow importing calibre extension modules using import statements from calibre_extensions import speedup --- src/calibre/constants.py | 82 +++++++++---------------------------- src/calibre/startup.py | 87 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 65 deletions(-) diff --git a/src/calibre/constants.py b/src/calibre/constants.py index d7e220cc5b..b7993def9e 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # vim:fileencoding=utf-8 # License: GPLv3 Copyright: 2015, Kovid Goyal -from polyglot.builtins import map, unicode_type, environ_item, hasenv, getenv, as_unicode, native_string_type +from polyglot.builtins import map, unicode_type, environ_item, hasenv, getenv import sys, locale, codecs, os, importlib, collections __appname__ = 'calibre' @@ -177,76 +177,32 @@ plugins_loc = sys.extensions_location class Plugins(collections.Mapping): - def __init__(self): - self._plugins = {} - plugins = [ - 'pictureflow', - 'lzx', - 'msdes', - 'podofo', - 'cPalmdoc', - 'progress_indicator', - 'icu', - 'speedup', - 'html_as_json', - 'unicode_names', - 'html_syntax_highlighter', - 'hyphen', - 'freetype', - 'imageops', - 'hunspell', - '_patiencediff_c', - 'bzzdec', - 'matcher', - 'tokenizer', - 'certgen', - ] - if iswindows: - plugins.extend(['winutil', 'wpd', 'winfonts']) - if ismacos: - plugins.append('usbobserver') - plugins.append('cocoa') - if isfreebsd or ishaiku or islinux or ismacos: - plugins.append('libusb') - plugins.append('libmtp') - self.plugins = frozenset(plugins) - - def load_plugin(self, name): - if name in self._plugins: - return - if not isfrozen: - sys.path.insert(0, plugins_loc) - try: - del sys.modules[name] - except KeyError: - pass - plugin_err = '' - try: - p = importlib.import_module(name) - except Exception as err: - p = None - try: - plugin_err = unicode_type(err) - except Exception: - plugin_err = as_unicode(native_string_type(err), encoding=preferred_encoding, errors='replace') - self._plugins[name] = p, plugin_err - if not isfrozen: - sys.path.remove(plugins_loc) - def __iter__(self): - return iter(self.plugins) + from importlib.resources import contents + return contents('calibre_extensions') def __len__(self): - return len(self.plugins) + from importlib.resources import contents + ans = 0 + for x in contents('calibre_extensions'): + ans += 1 + return ans def __contains__(self, name): - return name in self.plugins + from importlib.resources import contents + for x in contents('calibre_extensions'): + if x == name: + return True + return False def __getitem__(self, name): - if name not in self.plugins: + from importlib import import_module + try: + return import_module('calibre_extensions.' + name), None + except ModuleNotFoundError: raise KeyError('No plugin named %r'%name) - self.load_plugin(name) - return self._plugins[name] + except Exception as err: + return None, str(err) plugins = None diff --git a/src/calibre/startup.py b/src/calibre/startup.py index 4878595ae8..91d1f93a9d 100644 --- a/src/calibre/startup.py +++ b/src/calibre/startup.py @@ -21,7 +21,7 @@ builtins.__dict__['__'] = lambda s: s builtins.__dict__['dynamic_property'] = lambda func: func(None) -from calibre.constants import iswindows, preferred_encoding, plugins, ismacos, islinux, DEBUG, isfreebsd +from calibre.constants import iswindows, preferred_encoding, plugins, ismacos, islinux, ishaiku, DEBUG, isfreebsd, plugins_loc _run_once = False winutil = winutilerror = None @@ -49,7 +49,7 @@ def get_debug_executable(): if not _run_once: _run_once = True - from importlib.machinery import ModuleSpec + from importlib.machinery import ModuleSpec, EXTENSION_SUFFIXES, ExtensionFileLoader from importlib.util import find_spec from importlib import import_module @@ -81,6 +81,89 @@ if not _run_once: sys.meta_path.insert(0, DeVendor()) + class ExtensionsPackageLoader: + + def __init__(self, calibre_extensions): + self.calibre_extensions = calibre_extensions + + def is_package(self, fullname=None): + return True + + def get_resource_reader(self, fullname=None): + return self + + def get_source(self, fullname=None): + return '' + + def contents(self): + return iter(self.calibre_extensions) + + def create_module(self, spec): + pass + + def exec_module(self, spec): + pass + + class ExtensionsImporter: + + def __init__(self): + extensions = ( + 'pictureflow', + 'lzx', + 'msdes', + 'podofo', + 'cPalmdoc', + 'progress_indicator', + 'icu', + 'speedup', + 'html_as_json', + 'unicode_names', + 'html_syntax_highlighter', + 'hyphen', + 'freetype', + 'imageops', + 'hunspell', + '_patiencediff_c', + 'bzzdec', + 'matcher', + 'tokenizer', + 'certgen', + ) + if iswindows: + extra = ('winutil', 'wpd', 'winfonts') + elif ismacos: + extra = ('usbobserver', 'cocoa') + elif isfreebsd or ishaiku or islinux or ismacos: + extra = ('libusb', 'libmtp') + else: + extra = () + self.calibre_extensions = frozenset(extensions + extra) + + def find_spec(self, fullname, path=None, target=None): + if not fullname.startswith('calibre_extensions'): + return + parts = fullname.split('.') + if parts[0] != 'calibre_extensions': + return + if len(parts) > 2: + return + is_package = len(parts) == 1 + extension_name = None if is_package else parts[1] + path = os.path.join(plugins_loc, '__init__.py') + if extension_name: + if extension_name not in self.calibre_extensions: + return + for suffix in EXTENSION_SUFFIXES: + path = os.path.join(plugins_loc, extension_name + suffix) + if os.path.exists(path): + break + else: + return + return ModuleSpec(fullname, ExtensionFileLoader(fullname, path), is_package=is_package, origin=path) + return ModuleSpec(fullname, ExtensionsPackageLoader(self.calibre_extensions), is_package=is_package, origin=path) + + sys.meta_path.append(ExtensionsImporter()) + # Ensure that all temp files/dirs are created under a calibre tmp dir from calibre.ptempfile import base_dir try: