%PDF- %PDF-
Direktori : /lib/calibre/calibre/gui2/store/ |
Current File : //lib/calibre/calibre/gui2/store/loader.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' __docformat__ = 'restructuredtext en' import sys, time, io, re from zlib import decompressobj from collections import OrderedDict from threading import Thread from calibre import prints from calibre.constants import numeric_version, DEBUG from calibre.gui2.store import StorePlugin from calibre.utils.config import JSONConfig from polyglot.urllib import urlencode from polyglot.builtins import iteritems, itervalues class VersionMismatch(ValueError): def __init__(self, ver): ValueError.__init__(self, 'calibre too old') self.ver = ver def download_updates(ver_map={}, server='https://code.calibre-ebook.com'): from calibre.utils.https import get_https_resource_securely data = {k:str(v) for k, v in iteritems(ver_map)} data['ver'] = '1' url = '%s/stores?%s'%(server, urlencode(data)) # We use a timeout here to ensure the non-daemonic update thread does not # cause calibre to hang indefinitely during shutdown raw = get_https_resource_securely(url, timeout=90.0) while raw: name, raw = raw.partition(b'\0')[0::2] name = name.decode('utf-8') d = decompressobj() src = d.decompress(raw) src = src.decode('utf-8').lstrip('\ufeff') # Python complains if there is a coding declaration in a unicode string src = re.sub(r'^#.*coding\s*[:=]\s*([-\w.]+)', '#', src, flags=re.MULTILINE) # Translate newlines to \n src = io.StringIO(src, newline=None).getvalue() yield name, src raw = d.unused_data class Stores(OrderedDict): CHECK_INTERVAL = 24 * 60 * 60 def builtins_loaded(self): self.last_check_time = 0 self.version_map = {} self.cached_version_map = {} self.name_rmap = {} for key, val in iteritems(self): prefix, name = val.__module__.rpartition('.')[0::2] if prefix == 'calibre.gui2.store.stores' and name.endswith('_plugin'): module = sys.modules[val.__module__] sv = getattr(module, 'store_version', None) if sv is not None: name = name.rpartition('_')[0] self.version_map[name] = sv self.name_rmap[name] = key self.cache_file = JSONConfig('store/plugin_cache') self.load_cache() def load_cache(self): # Load plugins from on disk cache remove = set() pat = re.compile(r'^store_version\s*=\s*(\d+)', re.M) for name, src in iteritems(self.cache_file): try: key = self.name_rmap[name] except KeyError: # Plugin has been disabled m = pat.search(src[:512]) if m is not None: try: self.cached_version_map[name] = int(m.group(1)) except (TypeError, ValueError): pass continue try: obj, ver = self.load_object(src, key) except VersionMismatch as e: self.cached_version_map[name] = e.ver continue except: import traceback prints('Failed to load cached store:', name) traceback.print_exc() else: if not self.replace_plugin(ver, name, obj, 'cached'): # Builtin plugin is newer than cached remove.add(name) if remove: with self.cache_file: for name in remove: del self.cache_file[name] def check_for_updates(self): if hasattr(self, 'update_thread') and self.update_thread.is_alive(): return if time.time() - self.last_check_time < self.CHECK_INTERVAL: return self.last_check_time = time.time() try: self.update_thread.start() except (RuntimeError, AttributeError): self.update_thread = Thread(target=self.do_update) self.update_thread.start() def join(self, timeout=None): hasattr(self, 'update_thread') and self.update_thread.join(timeout) def download_updates(self): ver_map = {name:max(ver, self.cached_version_map.get(name, -1)) for name, ver in iteritems(self.version_map)} try: updates = download_updates(ver_map) except: import traceback traceback.print_exc() else: yield from updates def do_update(self): replacements = {} for name, src in self.download_updates(): try: key = self.name_rmap[name] except KeyError: # Plugin has been disabled replacements[name] = src continue try: obj, ver = self.load_object(src, key) except VersionMismatch as e: self.cached_version_map[name] = e.ver replacements[name] = src continue except: import traceback prints('Failed to load downloaded store:', name) traceback.print_exc() else: if self.replace_plugin(ver, name, obj, 'downloaded'): replacements[name] = src if replacements: with self.cache_file: for name, src in iteritems(replacements): self.cache_file[name] = src def replace_plugin(self, ver, name, obj, source): if ver > self.version_map[name]: if DEBUG: prints('Loaded', source, 'store plugin for:', self.name_rmap[name], 'at version:', ver) self[self.name_rmap[name]] = obj self.version_map[name] = ver return True return False def load_object(self, src, key): namespace = {} builtin = self[key] exec(src, namespace) ver = namespace['store_version'] cls = None for x in itervalues(namespace): if (isinstance(x, type) and issubclass(x, StorePlugin) and x is not StorePlugin): cls = x break if cls is None: raise ValueError('No store plugin found') if cls.minimum_calibre_version > numeric_version: raise VersionMismatch(ver) return cls(builtin.gui, builtin.name, config=builtin.config, base_plugin=builtin.base_plugin), ver if __name__ == '__main__': st = time.time() count = 0 for name, code in download_updates(): count += 1 print(name) print(code.encode('utf-8')) print('\n', '_'*80, '\n', sep='') print('Time to download all %d plugins: %.2f seconds'%(count, time.time() - st))