def set_book_path(path, pathtoebook): set_book_path.pathtoebook = pathtoebook set_book_path.path = os.path.abspath(path) set_book_path.metadata = get_data('calibre-book-metadata.json')[0] set_book_path.manifest, set_book_path.manifest_mime = get_data('calibre-book-manifest.json') set_book_path.parsed_metadata = json_loads(set_book_path.metadata) set_book_path.parsed_manifest = json_loads(set_book_path.manifest)
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 get_stored_annotations(container, bookmark_data): raw = bookmark_data or b'' if not raw: return if raw.startswith(EPUB_FILE_TYPE_MAGIC): raw = raw[len(EPUB_FILE_TYPE_MAGIC):].replace(b'\n', b'') for annot in json_loads(from_base64_bytes(raw)): yield annot return from calibre.ebooks.oeb.iterator.bookmarks import parse_bookmarks for bm in parse_bookmarks(raw): if bm['type'] == 'cfi' and isinstance(bm['pos'], unicode_type): spine_index = (1 + bm['spine']) * 2 epubcfi = 'epubcfi(/{}/{})'.format(spine_index, bm['pos'].lstrip('/')) title = bm.get('title') if title and title != 'calibre_current_page_bookmark': yield { 'type': 'bookmark', 'title': title, 'pos': epubcfi, 'pos_type': 'epubcfi', 'timestamp': EPOCH } else: yield { 'type': 'last-read', 'pos': epubcfi, 'pos_type': 'epubcfi', 'timestamp': EPOCH }
def cdb_run(ctx, rd, which, version): try: m = module_for_cmd(which) except ImportError: raise HTTPNotFound('No module named: {}'.format(which)) if not getattr(m, 'readonly', False): ctx.check_for_write_access(rd) if getattr(m, 'version', 0) != int(version): raise HTTPNotFound(('The module {} is not available in version: {}.' 'Make sure the version of calibre used for the' ' server and calibredb match').format(which, version)) db = get_library_data(ctx, rd, strict_library_id=True)[0] if ctx.restriction_for(rd, db): raise HTTPForbidden('Cannot use the command-line db interface with a user who has per library restrictions') raw = rd.read() ct = rd.inheaders.get('Content-Type', all=True) try: if MSGPACK_MIME in ct: args = msgpack_loads(raw) elif 'application/json' in ct: args = json_loads(raw) else: raise HTTPBadRequest('Only JSON or msgpack requests are supported') except Exception: raise HTTPBadRequest('args are not valid encoded data') if getattr(m, 'needs_srv_ctx', False): args = [ctx] + list(args) try: result = m.implementation(db, partial(ctx.notify_changes, db.backend.library_path), *args) except Exception as err: import traceback return {'err': as_unicode(err), 'tb': traceback.format_exc()} return {'result': result}
def dragMoveEvent(self, event): QTreeView.dragMoveEvent(self, event) self.setDropIndicatorShown(False) index = self.indexAt(event.pos()) if not index.isValid(): return src_is_tb = event.mimeData().hasFormat('application/calibre+from_tag_browser') item = index.data(Qt.UserRole) if item.type == TagTreeItem.ROOT: return flags = self._model.flags(index) if item.type == TagTreeItem.TAG and flags & Qt.ItemIsDropEnabled: self.setDropIndicatorShown(not src_is_tb) return if item.type == TagTreeItem.CATEGORY and not item.is_gst: fm_dest = self.db.metadata_for_field(item.category_key) if fm_dest['kind'] == 'user': if src_is_tb: if event.dropAction() == Qt.MoveAction: data = bytes(event.mimeData().data('application/calibre+from_tag_browser')) src = json_loads(data) for s in src: if s[0] == TagTreeItem.TAG and \ (not s[1].startswith('@') or s[2]): return self.setDropIndicatorShown(True) return md = event.mimeData() if hasattr(md, 'column_name'): fm_src = self.db.metadata_for_field(md.column_name) if md.column_name in ['authors', 'publisher', 'series'] or \ (fm_src['is_custom'] and ( (fm_src['datatype'] in ['series', 'text', 'enumeration'] and not fm_src['is_multiple']) or ( fm_src['datatype'] == 'composite' and fm_src['display'].get('make_category', False)))): self.setDropIndicatorShown(True)
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 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 load_book_annotations(self): amap = self.current_book_data['annotations_map'] path = os.path.join(self.current_book_data['base'], 'calibre-book-annotations.json') if os.path.exists(path): with open(path, 'rb') as f: raw = f.read() merge_annotations(json_loads(raw), amap) path = os.path.join(annotations_dir, self.current_book_data['annotations_path_key']) if os.path.exists(path): with open(path, 'rb') as f: raw = f.read() merge_annotations(parse_annotations(raw), amap)
def cdb_set_fields(ctx, rd, book_id, library_id): db = get_db(ctx, rd, library_id) if ctx.restriction_for(rd, db): raise HTTPForbidden( 'Cannot use the set fields interface with a user who has per library restrictions' ) raw = rd.read() ct = rd.inheaders.get('Content-Type', all=True) ct = {x.lower().partition(';')[0] for x in ct} try: if MSGPACK_MIME in ct: data = msgpack_loads(raw) elif 'application/json' in ct: data = json_loads(raw) else: raise HTTPBadRequest('Only JSON or msgpack requests are supported') except Exception: raise HTTPBadRequest('Invalid encoded data') try: changes, loaded_book_ids = data['changes'], frozenset( map(int, data.get('loaded_book_ids', ()))) all_dirtied = bool(data.get('all_dirtied')) if not isinstance(changes, dict): raise TypeError('changes must be a dict') except Exception: raise HTTPBadRequest( '''Data must be of the form {'changes': {'title': 'New Title', ...}, 'loaded_book_ids':[book_id1, book_id2, ...]'}''' ) dirtied = set() cdata = changes.pop('cover', False) if cdata is not False: if cdata is not None: try: cdata = standard_b64decode( cdata.split(',', 1)[-1].encode('ascii')) except Exception: raise HTTPBadRequest( 'Cover data is not valid base64 encoded data') try: fmt = what(None, cdata) except Exception: fmt = None if fmt not in ('jpeg', 'png'): raise HTTPBadRequest('Cover data must be either JPEG or PNG') dirtied |= db.set_cover({book_id: cdata}) for field, value in changes.iteritems(): dirtied |= db.set_field(field, {book_id: value}) ctx.notify_changes(db.backend.library_path, metadata(dirtied)) all_ids = dirtied if all_dirtied else (dirtied & loaded_book_ids) all_ids |= {book_id} return {bid: book_as_json(db, book_id) for bid in all_ids}
def load_payload_data(rd): raw = rd.read() ct = rd.inheaders.get('Content-Type', all=True) ct = {x.lower().partition(';')[0] for x in ct} try: if MSGPACK_MIME in ct: return msgpack_loads(raw) elif 'application/json' in ct: return json_loads(raw) else: raise HTTPBadRequest('Only JSON or msgpack requests are supported') except Exception: raise HTTPBadRequest('Invalid encoded data')
def test_serialize_metadata(self): # {{{ from calibre.utils.serialize import json_dumps, json_loads, msgpack_dumps, msgpack_loads from calibre.library.field_metadata import fm_as_dict cache = self.init_cache(self.library_path) fm = cache.field_metadata for d, l in ((json_dumps, json_loads), (msgpack_dumps, msgpack_loads)): fm2 = l(d(fm)) self.assertEqual(fm_as_dict(fm), fm_as_dict(fm2)) for i in range(1, 4): mi = cache.get_metadata(i, get_cover=True, cover_as_data=True) rmi = msgpack_loads(msgpack_dumps(mi)) self.compare_metadata(mi, rmi, exclude='format_metadata has_cover formats id'.split()) rmi = json_loads(json_dumps(mi)) self.compare_metadata(mi, rmi, exclude='format_metadata has_cover formats id'.split())
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 cdb_set_fields(ctx, rd, book_id, library_id): db = get_db(ctx, rd, library_id) if ctx.restriction_for(rd, db): raise HTTPForbidden('Cannot use the set fields interface with a user who has per library restrictions') raw = rd.read() ct = rd.inheaders.get('Content-Type', all=True) ct = {x.lower().partition(';')[0] for x in ct} try: if MSGPACK_MIME in ct: data = msgpack_loads(raw) elif 'application/json' in ct: data = json_loads(raw) else: raise HTTPBadRequest('Only JSON or msgpack requests are supported') changes, loaded_book_ids = data['changes'], frozenset(map(int, data['loaded_book_ids'])) except Exception: raise HTTPBadRequest('Invalid encoded data') dirtied = set() for field, value in changes.iteritems(): dirtied |= db.set_field(field, {book_id: value}) metadata(dirtied) return {bid: book_as_json(db, book_id) for bid in (dirtied & loaded_book_ids) | {book_id}}
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 cdb_set_fields(ctx, rd, book_id, library_id): db = get_db(ctx, rd, library_id) if ctx.restriction_for(rd, db): raise HTTPForbidden('Cannot use the set fields interface with a user who has per library restrictions') raw = rd.read() ct = rd.inheaders.get('Content-Type', all=True) ct = {x.lower().partition(';')[0] for x in ct} try: if MSGPACK_MIME in ct: data = msgpack_loads(raw) elif 'application/json' in ct: data = json_loads(raw) else: raise HTTPBadRequest('Only JSON or msgpack requests are supported') changes, loaded_book_ids = data['changes'], frozenset(map(int, data['loaded_book_ids'])) except Exception: raise HTTPBadRequest('Invalid encoded data') dirtied = set() cdata = changes.pop('cover', False) if cdata is not False: if cdata is not None: try: cdata = standard_b64decode(cdata.split(',', 1)[-1].encode('ascii')) except Exception: raise HTTPBadRequest('Cover data is not valid base64 encoded data') try: fmt = what(None, cdata) except Exception: fmt = None if fmt not in ('jpeg', 'png'): raise HTTPBadRequest('Cover data must be either JPEG or PNG') dirtied |= db.set_cover({book_id: cdata}) for field, value in changes.iteritems(): dirtied |= db.set_field(field, {book_id: value}) ctx.notify_changes(db.backend.library_path, metadata(dirtied)) return {bid: book_as_json(db, book_id) for bid in (dirtied & loaded_book_ids) | {book_id}}
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 parse_annotations(raw): for annot in json_loads(raw): yield parse_annotation(annot)
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 serialized_prefs(self, val: bytes) -> None: from calibre.utils.serialize import json_loads prefs = json_loads(val) self.apply_prefs(prefs) self.update_preview()
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())