def main(): # The entry point for the simple worker process address = msgpack_loads( from_hex_bytes(os.environ['CALIBRE_WORKER_ADDRESS'])) key = from_hex_bytes(os.environ['CALIBRE_WORKER_KEY']) with closing(Client(address, authkey=key)) as conn: args = eintr_retry_call(conn.recv) try: mod, func, args, kwargs, module_is_source_code = args if module_is_source_code: importlib.import_module('calibre.customize.ui') # Load plugins mod = compile_code(mod) func = mod[func] else: try: mod = importlib.import_module(mod) except ImportError: importlib.import_module( 'calibre.customize.ui') # Load plugins mod = importlib.import_module(mod) func = getattr(mod, func) res = {'result': func(*args, **kwargs)} except: res = {'tb': traceback.format_exc()} try: conn.send(res) except: # Maybe EINTR conn.send(res)
def main(): # The entry point for the simple worker process address = msgpack_loads(from_hex_bytes(os.environ['CALIBRE_WORKER_ADDRESS'])) key = from_hex_bytes(os.environ['CALIBRE_WORKER_KEY']) with closing(Client(address, authkey=key)) as conn: args = eintr_retry_call(conn.recv) try: mod, func, args, kwargs, module_is_source_code = args if module_is_source_code: importlib.import_module('calibre.customize.ui') # Load plugins mod = compile_code(mod) func = mod[func] else: try: mod = importlib.import_module(mod) except ImportError: importlib.import_module('calibre.customize.ui') # Load plugins mod = importlib.import_module(mod) func = getattr(mod, func) res = {'result':func(*args, **kwargs)} except: res = {'tb': traceback.format_exc()} try: conn.send(res) except: # Maybe EINTR conn.send(res)
def offload(): # The entry point for the offload worker process address = msgpack_loads(from_hex_bytes(os.environ['CALIBRE_WORKER_ADDRESS'])) key = from_hex_bytes(os.environ['CALIBRE_WORKER_KEY']) func_cache = {} with closing(Client(address, authkey=key)) as conn: while True: args = eintr_retry_call(conn.recv) if args is None: break res = {'result':None, 'tb':None} try: mod, func, args, kwargs = args if mod is None: eintr_retry_call(conn.send, res) continue f = func_cache.get((mod, func), None) if f is None: try: m = importlib.import_module(mod) except ImportError: importlib.import_module('calibre.customize.ui') # Load plugins m = importlib.import_module(mod) func_cache[(mod, func)] = f = getattr(m, func) res['result'] = f(*args, **kwargs) except: import traceback res['tb'] = traceback.format_exc() eintr_retry_call(conn.send, res)
def offload(): # The entry point for the offload worker process address = msgpack_loads( from_hex_bytes(os.environ['CALIBRE_WORKER_ADDRESS'])) key = from_hex_bytes(os.environ['CALIBRE_WORKER_KEY']) func_cache = {} with closing(Client(address, authkey=key)) as conn: while True: args = eintr_retry_call(conn.recv) if args is None: break res = {'result': None, 'tb': None} try: mod, func, args, kwargs = args if mod is None: eintr_retry_call(conn.send, res) continue f = func_cache.get((mod, func), None) if f is None: try: m = importlib.import_module(mod) except ImportError: importlib.import_module( 'calibre.customize.ui') # Load plugins m = importlib.import_module(mod) func_cache[(mod, func)] = f = getattr(m, func) res['result'] = f(*args, **kwargs) except: import traceback res['tb'] = traceback.format_exc() eintr_retry_call(conn.send, res)
def windows_repair(library_path=None): import subprocess from calibre.utils.serialize import json_dumps, json_loads from polyglot.binary import as_hex_unicode, from_hex_bytes if library_path: library_path = as_hex_unicode(json_dumps(library_path)) winutil.prepare_for_restart() os.environ['CALIBRE_REPAIR_CORRUPTED_DB'] = environ_item(library_path) subprocess.Popen([sys.executable]) else: try: app = Application([]) from calibre.gui2.dialogs.restore_library import repair_library_at library_path = json_loads( from_hex_bytes(os.environ.pop('CALIBRE_REPAIR_CORRUPTED_DB'))) done = repair_library_at(library_path, wait_time=4) except Exception: done = False error_dialog( None, _('Failed to repair library'), _('Could not repair library. Click "Show details" for more information.' ), det_msg=traceback.format_exc(), show=True) if done: subprocess.Popen([sys.executable]) app.quit()
def details_context_menu_event(view, ev, book_info, add_popup_action=False): url = view.anchorAt(ev.pos()) menu = QMenu(view) menu.addAction(QIcon(I('edit-copy.png')), _('Copy all book details'), partial(copy_all, view)) search_internet_added = False if url and url.startswith('action:'): data = json_loads(from_hex_bytes(url.split(':', 1)[1])) search_internet_added = add_item_specific_entries( menu, data, book_info) elif url and not url.startswith('#'): ac = book_info.copy_link_action ac.current_url = url ac.setText(_('Copy link location')) menu.addAction(ac) if not search_internet_added and hasattr(book_info, 'search_internet'): menu.addSeparator() menu.si = create_search_internet_menu(book_info.search_internet) menu.addMenu(menu.si) for ac in tuple(menu.actions()): if not ac.isEnabled(): menu.removeAction(ac) if add_popup_action: menu.addSeparator() ac = menu.addAction(_('Open the Book details window')) ac.triggered.connect(book_info.show_book_info) if len(menu.actions()) > 0: menu.exec_(ev.globalPos())
def is_nonce_stale(nonce, max_age_seconds=MAX_AGE_SECONDS): try: timestamp = struct.unpack(b'!dH', from_hex_bytes(as_bytestring(nonce.partition(':')[0])))[0] return timestamp + max_age_seconds < monotonic() except Exception: pass return True
def details_context_menu_event(view, ev, book_info, add_popup_action=False, edit_metadata=None): url = view.anchorAt(ev.pos()) menu = QMenu(view) copy_menu = menu.addMenu(QIcon(I('edit-copy.png')), _('Copy')) copy_menu.addAction(QIcon(I('edit-copy.png')), _('All book details'), partial(copy_all, view)) if view.textCursor().hasSelection(): copy_menu.addAction(QIcon(I('edit-copy.png')), _('Selected text'), view.copy) copy_menu.addSeparator() copy_links_added = False search_internet_added = False search_menu = QMenu(_('Search'), menu) search_menu.setIcon(QIcon(I('search.png'))) if url and url.startswith('action:'): data = json_loads(from_hex_bytes(url.split(':', 1)[1])) search_internet_added = add_item_specific_entries( menu, data, book_info, copy_menu, search_menu) create_copy_links(copy_menu, data) copy_links_added = True elif url and not url.startswith('#'): ac = book_info.copy_link_action ac.current_url = url ac.setText(_('Copy link location')) menu.addAction(ac) if not copy_links_added: create_copy_links(copy_menu) if not search_internet_added and hasattr(book_info, 'search_internet'): sim = create_search_internet_menu(book_info.search_internet) if search_menu.isEmpty(): search_menu = sim else: search_menu.addSeparator() for ac in sim.actions(): search_menu.addAction(ac) ac.setText(_('Search {0} for this book').format(ac.text())) if not search_menu.isEmpty(): menu.addMenu(search_menu) for ac in tuple(menu.actions()): if not ac.isEnabled(): menu.removeAction(ac) menu.addSeparator() if add_popup_action: ac = menu.addAction(_('Open the Book details window')) ac.triggered.connect(book_info.show_book_info) else: from calibre.gui2.ui import get_gui ema = get_gui().iactions['Edit Metadata'].menuless_qaction menu.addAction( _('Open the Edit metadata window') + '\t' + ema.shortcut().toString(QKeySequence.SequenceFormat.NativeText), edit_metadata) if len(menu.actions()) > 0: menu.exec_(ev.globalPos())
def update_link_clicked(self, url): url = str(url) if url.startswith('update:'): calibre_version, number_of_plugin_updates = msgpack_loads( from_hex_bytes(url[len('update:'):])) self.update_found(calibre_version, number_of_plugin_updates, force=True)
def base_dir(): global _base_dir if _base_dir is not None and not os.path.exists(_base_dir): # Some people seem to think that running temp file cleaners that # delete the temp dirs of running programs is a good idea! _base_dir = None if _base_dir is None: td = os.environ.get('CALIBRE_WORKER_TEMP_DIR', None) if td is not None: from calibre.utils.serialize import msgpack_loads from polyglot.binary import from_hex_bytes try: td = msgpack_loads(from_hex_bytes(td)) except Exception: td = None if td and os.path.exists(td): _base_dir = td else: base = os.environ.get('CALIBRE_TEMP_DIR', None) if base is not None and iswindows: base = getenv('CALIBRE_TEMP_DIR') prefix = app_prefix('tmp_') if base is None: if iswindows: # On windows, if the TMP env var points to a path that # cannot be encoded using the mbcs encoding, then the # python 2 tempfile algorithm for getting the temporary # directory breaks. So we use the win32 api to get a # unicode temp path instead. See # https://bugs.launchpad.net/bugs/937389 base = get_windows_temp_path() elif isosx: # Use the cache dir rather than the temp dir for temp files as Apple # thinks deleting unused temp files is a good idea. See note under # _CS_DARWIN_USER_TEMP_DIR here # https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/confstr.3.html base = osx_cache_dir() _base_dir = tempfile.mkdtemp(prefix=prefix, dir=base) atexit.register(determined_remove_dir if iswindows else remove_dir, _base_dir) try: tempfile.gettempdir() except Exception: # Widows temp vars set to a path not encodable in mbcs # Use our temp dir tempfile.tempdir = _base_dir return _base_dir
def base_dir(): global _base_dir if _base_dir is not None and not os.path.exists(_base_dir): # Some people seem to think that running temp file cleaners that # delete the temp dirs of running programs is a good idea! _base_dir = None if _base_dir is None: td = os.environ.get('CALIBRE_WORKER_TEMP_DIR', None) if td is not None: from calibre.utils.serialize import msgpack_loads from polyglot.binary import from_hex_bytes try: td = msgpack_loads(from_hex_bytes(td)) except Exception: td = None if td and os.path.exists(td): _base_dir = td else: base = os.environ.get('CALIBRE_TEMP_DIR', None) if base is not None and iswindows: base = getenv('CALIBRE_TEMP_DIR') prefix = app_prefix('tmp_') if base is None: if iswindows: # On windows, if the TMP env var points to a path that # cannot be encoded using the mbcs encoding, then the # python 2 tempfile algorithm for getting the temporary # directory breaks. So we use the win32 api to get a # unicode temp path instead. See # https://bugs.launchpad.net/bugs/937389 base = get_windows_temp_path() elif isosx: # Use the cache dir rather than the temp dir for temp files as Apple # thinks deleting unused temp files is a good idea. See note under # _CS_DARWIN_USER_TEMP_DIR here # https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/confstr.3.html base = osx_cache_dir() _base_dir = tempfile.mkdtemp(prefix=prefix, dir=base) atexit.register(determined_remove_dir if iswindows else remove_dir, _base_dir) try: tempfile.gettempdir() except: # Widows temp vars set to a path not encodable in mbcs # Use our temp dir tempfile.tempdir = _base_dir return _base_dir
def encrypt_fonts(self, uris, tdir, uuid): # {{{ from polyglot.binary import from_hex_bytes key = re.sub(r'[^a-fA-F0-9]', '', uuid) if len(key) < 16: raise ValueError('UUID identifier %r is invalid' % uuid) key = bytearray(from_hex_bytes((key + key)[:32])) paths = [] with CurrentDir(tdir): paths = [os.path.join(*x.split('/')) for x in uris] uris = dict(zip(uris, paths)) fonts = [] for uri in list(uris.keys()): path = uris[uri] if not os.path.exists(path): uris.pop(uri) continue self.log.debug('Encrypting font:', uri) with lopen(path, 'r+b') as f: data = f.read(1024) if len(data) >= 1024: data = bytearray(data) f.seek(0) f.write( bytes( bytearray(data[i] ^ key[i % 16] for i in range(1024)))) else: self.log.warn('Font', path, 'is invalid, ignoring') if not isinstance(uri, unicode_type): uri = uri.decode('utf-8') fonts.append(''' <enc:EncryptedData> <enc:EncryptionMethod Algorithm="http://ns.adobe.com/pdf/enc#RC"/> <enc:CipherData> <enc:CipherReference URI="%s"/> </enc:CipherData> </enc:EncryptedData> ''' % (uri.replace('"', '\\"'))) if fonts: ans = '''<encryption xmlns="urn:oasis:names:tc:opendocument:xmlns:container" xmlns:enc="http://www.w3.org/2001/04/xmlenc#" xmlns:deenc="http://ns.adobe.com/digitaleditions/enc"> ''' ans += '\n'.join(fonts) ans += '\n</encryption>' return ans
def encrypt_fonts(self, uris, tdir, uuid): # {{{ from polyglot.binary import from_hex_bytes key = re.sub(r'[^a-fA-F0-9]', '', uuid) if len(key) < 16: raise ValueError('UUID identifier %r is invalid'%uuid) key = bytearray(from_hex_bytes((key + key)[:32])) paths = [] with CurrentDir(tdir): paths = [os.path.join(*x.split('/')) for x in uris] uris = dict(zip(uris, paths)) fonts = [] for uri in list(uris.keys()): path = uris[uri] if not os.path.exists(path): uris.pop(uri) continue self.log.debug('Encrypting font:', uri) with lopen(path, 'r+b') as f: data = f.read(1024) if len(data) >= 1024: f.seek(0) for i in range(1024): f.write(chr(ord(data[i]) ^ key[i%16])) else: self.log.warn('Font', path, 'is invalid, ignoring') if not isinstance(uri, unicode_type): uri = uri.decode('utf-8') fonts.append(u''' <enc:EncryptedData> <enc:EncryptionMethod Algorithm="http://ns.adobe.com/pdf/enc#RC"/> <enc:CipherData> <enc:CipherReference URI="%s"/> </enc:CipherData> </enc:EncryptedData> '''%(uri.replace('"', '\\"'))) if fonts: ans = '''<encryption xmlns="urn:oasis:names:tc:opendocument:xmlns:container" xmlns:enc="http://www.w3.org/2001/04/xmlenc#" xmlns:deenc="http://ns.adobe.com/digitaleditions/enc"> ''' ans += (u'\n'.join(fonts)).encode('utf-8') ans += '\n</encryption>' return ans
def handle_click(self, link): typ, val = link.partition(':')[::2] def search_term(field, val): append = '' mods = QApplication.instance().keyboardModifiers() if mods & Qt.KeyboardModifier.ControlModifier: append = 'AND' if mods & Qt.KeyboardModifier.ShiftModifier else 'OR' fmt = '{}:{}' if is_boolean(field) else '{}:"={}"' self.search_requested.emit( fmt.format(field, val.replace('"', '\\"')), append ) def browse(url): try: safe_open_url(QUrl(url, QUrl.ParsingMode.TolerantMode)) except Exception: import traceback traceback.print_exc() if typ == 'action': data = json_loads(from_hex_bytes(val)) dt = data['type'] if dt == 'search': search_term(data['term'], data['value']) elif dt == 'author': url = data['url'] if url == 'calibre': search_term('authors', data['name']) else: browse(url) elif dt == 'format': book_id, fmt = data['book_id'], data['fmt'] self.view_specific_format.emit(int(book_id), fmt) elif dt == 'identifier': if data['url']: browse(data['url']) elif dt == 'path': self.open_containing_folder.emit(int(data['loc'])) elif dt == 'devpath': self.view_device_book.emit(data['loc']) else: browse(link)
def rule_from_template(fm, template): ok_lines = [] for line in template.splitlines(): if line.startswith(Rule.SIGNATURE): raw = line[len(Rule.SIGNATURE):].strip() try: color, conditions = json.loads(from_hex_bytes(raw)) except: continue r = Rule(fm) r.color = color for c in conditions: try: r.add_condition(*c) except: continue if r.color and r.conditions: return r else: ok_lines.append(line) return '\n'.join(ok_lines)
def windows_repair(library_path=None): import subprocess from calibre.utils.serialize import json_dumps, json_loads from polyglot.binary import as_hex_unicode, from_hex_bytes if library_path: library_path = as_hex_unicode(json_dumps(library_path)) winutil.prepare_for_restart() os.environ['CALIBRE_REPAIR_CORRUPTED_DB'] = environ_item(library_path) subprocess.Popen([sys.executable]) else: try: app = Application([]) from calibre.gui2.dialogs.restore_library import repair_library_at library_path = json_loads(from_hex_bytes(os.environ.pop('CALIBRE_REPAIR_CORRUPTED_DB'))) done = repair_library_at(library_path, wait_time=4) except Exception: done = False error_dialog(None, _('Failed to repair library'), _( 'Could not repair library. Click "Show details" for more information.'), det_msg=traceback.format_exc(), show=True) if done: subprocess.Popen([sys.executable]) app.quit()
def handle_click(self, link): typ, val = link.partition(':')[::2] def search_term(field, val): self.search_requested.emit('{}:"={}"'.format( field, val.replace('"', '\\"'))) def browse(url): try: safe_open_url(QUrl(url, QUrl.TolerantMode)) except Exception: import traceback traceback.print_exc() if typ == 'action': data = json_loads(from_hex_bytes(val)) dt = data['type'] if dt == 'search': search_term(data['term'], data['value']) elif dt == 'author': url = data['url'] if url == 'calibre': search_term('authors', data['name']) else: browse(url) elif dt == 'format': book_id, fmt = data['book_id'], data['fmt'] self.view_specific_format.emit(int(book_id), fmt) elif dt == 'identifier': if data['url']: browse(data['url']) elif dt == 'path': self.open_containing_folder.emit(int(data['loc'])) elif dt == 'devpath': self.view_device_book.emit(data['loc']) else: browse(link)
def main(): if iswindows: if '--multiprocessing-fork' in sys.argv: # We are using the multiprocessing module on windows to launch a # worker process from multiprocessing import freeze_support freeze_support() return 0 # Close open file descriptors inherited from parent # On Unix this is done by the subprocess module os.closerange(3, 256) if isosx and 'CALIBRE_WORKER_ADDRESS' not in os.environ and 'CALIBRE_SIMPLE_WORKER' not in os.environ and '--pipe-worker' not in sys.argv: # On some OS X computers launchd apparently tries to # launch the last run process from the bundle # so launch the gui as usual from calibre.gui2.main import main as gui_main return gui_main(['calibre']) csw = os.environ.get('CALIBRE_SIMPLE_WORKER', None) if csw: mod, _, func = csw.partition(':') mod = importlib.import_module(mod) func = getattr(mod, func) func() return if '--pipe-worker' in sys.argv: try: exec (sys.argv[-1]) except Exception: print('Failed to run pipe worker with command:', sys.argv[-1]) raise return address = msgpack_loads(from_hex_bytes(os.environ['CALIBRE_WORKER_ADDRESS'])) key = from_hex_bytes(os.environ['CALIBRE_WORKER_KEY']) resultf = from_hex_unicode(os.environ['CALIBRE_WORKER_RESULT']) with closing(Client(address, authkey=key)) as conn: name, args, kwargs, desc = eintr_retry_call(conn.recv) if desc: prints(desc) sys.stdout.flush() func, notification = get_func(name) notifier = Progress(conn) if notification: kwargs[notification] = notifier notifier.start() result = func(*args, **kwargs) if result is not None and os.path.exists(os.path.dirname(resultf)): with lopen(resultf, 'wb') as f: f.write(pickle_dumps(result)) notifier.queue.put(None) try: sys.stdout.flush() except EnvironmentError: pass # Happens sometimes on OS X for GUI processes (EPIPE) try: sys.stderr.flush() except EnvironmentError: pass # Happens sometimes on OS X for GUI processes (EPIPE) return 0
def hex2re(hex_data): return re.escape(from_hex_bytes(hex_data.replace(' ', '').encode('ascii')))
def details_context_menu_event(view, ev, book_info): # {{{ p = view.page() mf = p.mainFrame() r = mf.hitTestContent(ev.pos()) url = unicode_type(r.linkUrl().toString(NO_URL_FORMATTING)).strip() menu = p.createStandardContextMenu() ca = view.pageAction(p.Copy) for action in list(menu.actions()): if action is not ca: menu.removeAction(action) menu.addAction(QIcon(I('edit-copy.png')), _('Copy &all'), partial(copy_all, book_info)) search_internet_added = False if not r.isNull(): from calibre.ebooks.oeb.polish.main import SUPPORTED if url.startswith('format:'): parts = url.split(':') try: book_id, fmt = int(parts[1]), parts[2].upper() except: import traceback traceback.print_exc() else: from calibre.gui2.ui import get_gui db = get_gui().current_db.new_api ofmt = fmt.upper() if fmt.startswith('ORIGINAL_') else 'ORIGINAL_' + fmt nfmt = ofmt[len('ORIGINAL_'):] fmts = {x.upper() for x in db.formats(book_id)} for a, t in [ ('remove', _('Delete the %s format')), ('save', _('Save the %s format to disk')), ('restore', _('Restore the %s format')), ('compare', ''), ('set_cover', _('Set the book cover from the %s file')), ]: if a == 'restore' and not fmt.startswith('ORIGINAL_'): continue if a == 'compare': if ofmt not in fmts or nfmt not in SUPPORTED: continue t = _('Compare to the %s format') % (fmt[9:] if fmt.startswith('ORIGINAL_') else ofmt) else: t = t % fmt ac = getattr(book_info, '%s_format_action'%a) ac.current_fmt = (book_id, fmt) ac.setText(t) menu.addAction(ac) if not fmt.upper().startswith('ORIGINAL_'): from calibre.gui2.open_with import populate_menu, edit_programs m = QMenu(_('Open %s with...') % fmt.upper()) def connect_action(ac, entry): connect_lambda(ac.triggered, book_info, lambda book_info: book_info.open_with(book_id, fmt, entry)) populate_menu(m, connect_action, fmt) if len(m.actions()) == 0: menu.addAction(_('Open %s with...') % fmt.upper(), partial(book_info.choose_open_with, book_id, fmt)) else: m.addSeparator() m.addAction(_('Add other application for %s files...') % fmt.upper(), partial(book_info.choose_open_with, book_id, fmt)) m.addAction(_('Edit Open With applications...'), partial(edit_programs, fmt, book_info)) menu.addMenu(m) menu.ow = m if fmt.upper() in SUPPORTED: menu.addSeparator() menu.addAction(_('Edit %s...') % fmt.upper(), partial(book_info.edit_fmt, book_id, fmt)) ac = book_info.copy_link_action ac.current_url = r.linkElement().attribute('data-full-path') if ac.current_url: ac.setText(_('&Copy path to file')) menu.addAction(ac) else: el = r.linkElement() data = el.attribute('data-item') author = el.toPlainText() if unicode_type(el.attribute('calibre-data')) == u'authors' else None if url and not url.startswith('search:'): for a, t in [('copy', _('&Copy link')), ]: ac = getattr(book_info, '%s_link_action'%a) ac.current_url = url if url.startswith('path:'): ac.current_url = el.attribute('title') ac.setText(t) menu.addAction(ac) if author is not None: menu.addAction(init_manage_action(book_info.manage_action, 'authors', author)) if hasattr(book_info, 'search_internet'): menu.sia = sia = create_search_internet_menu(book_info.search_internet, author) menu.addMenu(sia) search_internet_added = True if hasattr(book_info, 'search_requested'): menu.addAction(_('Search calibre for %s') % author, lambda : book_info.search_requested('authors:"={}"'.format(author.replace('"', r'\"')))) if data: try: field, value, book_id = json_loads(from_hex_bytes(data)) except Exception: field = value = book_id = None if field: if author is None: if field in ('tags', 'series', 'publisher') or is_category(field): menu.addAction(init_manage_action(book_info.manage_action, field, value)) elif field == 'identifiers': menu.addAction(book_info.edit_identifiers_action) ac = book_info.remove_item_action ac.data = (field, value, book_id) ac.setText(_('Remove %s from this book') % value) menu.addAction(ac) if not search_internet_added and hasattr(book_info, 'search_internet'): menu.addSeparator() menu.si = create_search_internet_menu(book_info.search_internet) menu.addMenu(menu.si) if len(menu.actions()) > 0: menu.exec_(ev.globalPos())
def details_context_menu_event(view, ev, book_info): # {{{ p = view.page() mf = p.mainFrame() r = mf.hitTestContent(ev.pos()) url = unicode_type(r.linkUrl().toString(NO_URL_FORMATTING)).strip() menu = p.createStandardContextMenu() ca = view.pageAction(p.Copy) for action in list(menu.actions()): if action is not ca: menu.removeAction(action) menu.addAction(QIcon(I('edit-copy.png')), _('Copy &all'), partial(copy_all, book_info)) search_internet_added = False if not r.isNull(): from calibre.ebooks.oeb.polish.main import SUPPORTED if url.startswith('format:'): parts = url.split(':') try: book_id, fmt = int(parts[1]), parts[2].upper() except: import traceback traceback.print_exc() else: from calibre.gui2.ui import get_gui db = get_gui().current_db.new_api ofmt = fmt.upper() if fmt.startswith( 'ORIGINAL_') else 'ORIGINAL_' + fmt nfmt = ofmt[len('ORIGINAL_'):] fmts = {x.upper() for x in db.formats(book_id)} for a, t in [ ('remove', _('Delete the %s format')), ('save', _('Save the %s format to disk')), ('restore', _('Restore the %s format')), ('compare', ''), ('set_cover', _('Set the book cover from the %s file')), ]: if a == 'restore' and not fmt.startswith('ORIGINAL_'): continue if a == 'compare': if ofmt not in fmts or nfmt not in SUPPORTED: continue t = _('Compare to the %s format') % ( fmt[9:] if fmt.startswith('ORIGINAL_') else ofmt) else: t = t % fmt ac = getattr(book_info, '%s_format_action' % a) ac.current_fmt = (book_id, fmt) ac.setText(t) menu.addAction(ac) if not fmt.upper().startswith('ORIGINAL_'): from calibre.gui2.open_with import populate_menu, edit_programs m = QMenu(_('Open %s with...') % fmt.upper()) def connect_action(ac, entry): connect_lambda( ac.triggered, book_info, lambda book_info: book_info.open_with( book_id, fmt, entry)) populate_menu(m, connect_action, fmt) if len(m.actions()) == 0: menu.addAction( _('Open %s with...') % fmt.upper(), partial(book_info.choose_open_with, book_id, fmt)) else: m.addSeparator() m.addAction( _('Add other application for %s files...') % fmt.upper(), partial(book_info.choose_open_with, book_id, fmt)) m.addAction(_('Edit Open With applications...'), partial(edit_programs, fmt, book_info)) menu.addMenu(m) menu.ow = m if fmt.upper() in SUPPORTED: menu.addSeparator() menu.addAction( _('Edit %s...') % fmt.upper(), partial(book_info.edit_fmt, book_id, fmt)) ac = book_info.copy_link_action ac.current_url = r.linkElement().attribute('data-full-path') if ac.current_url: ac.setText(_('&Copy path to file')) menu.addAction(ac) else: el = r.linkElement() data = el.attribute('data-item') author = el.toPlainText() if unicode_type( el.attribute('calibre-data')) == 'authors' else None if url and not url.startswith('search:'): for a, t in [ ('copy', _('&Copy link')), ]: ac = getattr(book_info, '%s_link_action' % a) ac.current_url = url if url.startswith('path:'): ac.current_url = el.attribute('title') ac.setText(t) menu.addAction(ac) if author is not None: menu.addAction( init_manage_action(book_info.manage_action, 'authors', author)) if hasattr(book_info, 'search_internet'): menu.sia = sia = create_search_internet_menu( book_info.search_internet, author) menu.addMenu(sia) search_internet_added = True if hasattr(book_info, 'search_requested'): menu.addAction( _('Search calibre for %s') % author, lambda: book_info. search_requested('authors:"={}"'.format( author.replace('"', r'\"')))) if data: try: field, value, book_id = json_loads(from_hex_bytes(data)) except Exception: field = value = book_id = None if field: if author is None: if field in ('tags', 'series', 'publisher') or is_category(field): menu.addAction( init_manage_action(book_info.manage_action, field, value)) elif field == 'identifiers': menu.addAction(book_info.edit_identifiers_action) ac = book_info.remove_item_action ac.data = (field, value, book_id) ac.setText(_('Remove %s from this book') % value) menu.addAction(ac) if not search_internet_added and hasattr(book_info, 'search_internet'): menu.addSeparator() menu.si = create_search_internet_menu(book_info.search_internet) menu.addMenu(menu.si) if len(menu.actions()) > 0: menu.exec_(ev.globalPos())
def main(): if iswindows: if '--multiprocessing-fork' in sys.argv: # We are using the multiprocessing module on windows to launch a # worker process from multiprocessing import freeze_support freeze_support() return 0 # Close open file descriptors inherited from parent # On Unix this is done by the subprocess module os.closerange(3, 256) if isosx and 'CALIBRE_WORKER_ADDRESS' not in os.environ and 'CALIBRE_SIMPLE_WORKER' not in os.environ and '--pipe-worker' not in sys.argv: # On some OS X computers launchd apparently tries to # launch the last run process from the bundle # so launch the gui as usual from calibre.gui2.main import main as gui_main return gui_main(['calibre']) csw = os.environ.get('CALIBRE_SIMPLE_WORKER', None) if csw: mod, _, func = csw.partition(':') mod = importlib.import_module(mod) func = getattr(mod, func) func() return if '--pipe-worker' in sys.argv: try: exec(sys.argv[-1]) except Exception: print('Failed to run pipe worker with command:', sys.argv[-1]) raise return address = msgpack_loads(from_hex_bytes(os.environ['CALIBRE_WORKER_ADDRESS'])) key = from_hex_bytes(os.environ['CALIBRE_WORKER_KEY']) resultf = from_hex_unicode(os.environ['CALIBRE_WORKER_RESULT']) with closing(Client(address, authkey=key)) as conn: name, args, kwargs, desc = eintr_retry_call(conn.recv) if desc: prints(desc) sys.stdout.flush() func, notification = get_func(name) notifier = Progress(conn) if notification: kwargs[notification] = notifier notifier.start() result = func(*args, **kwargs) if result is not None and os.path.exists(os.path.dirname(resultf)): with lopen(resultf, 'wb') as f: f.write(pickle_dumps(result)) notifier.queue.put(None) try: sys.stdout.flush() except EnvironmentError: pass # Happens sometimes on OS X for GUI processes (EPIPE) try: sys.stderr.flush() except EnvironmentError: pass # Happens sometimes on OS X for GUI processes (EPIPE) return 0