def get_valid(prompt, invalidq=lambda x: None): while True: ans = raw_input(prompt + ": ").strip().decode(enc) fail_message = invalidq(ans) if fail_message is None: return ans prints(fail_message)
def _read_version_history_html(self, forum_link): br = browser() br.set_handle_gzip(True) try: raw = br.open_novisit(forum_link).read() if not raw: return None except: traceback.print_exc() return None raw = raw.decode('utf-8', errors='replace') root = html.fromstring(raw) spoiler_nodes = root.xpath('//div[@class="smallfont" and strong="Spoiler"]') for spoiler_node in spoiler_nodes: try: if spoiler_node.getprevious() is None: # This is a spoiler node that has been indented using [INDENT] # Need to go up to parent div, then previous node to get header heading_node = spoiler_node.getparent().getprevious() else: # This is a spoiler node after a BR tag from the heading heading_node = spoiler_node.getprevious().getprevious() if heading_node is None: continue if heading_node.text_content().lower().find('version history') != -1: div_node = spoiler_node.xpath('div')[0] text = html.tostring(div_node, method='html', encoding='unicode') return re.sub(r'<div\s.*?>', '<div>', text) except: if DEBUG: prints('======= MobileRead Parse Error =======') traceback.print_exc() prints(html.tostring(spoiler_node)) return None
def purge_broken_playlist_items(self, root): id_map = self.build_id_map(root) for pl in root.xpath('//*[local-name()="playlist"]'): seen = set([]) for item in list(pl): id_ = item.get("id", None) if id_ is None or id_ in seen or id_map.get(id_, None) is None: if DEBUG: if id_ is None: cause = "invalid id" elif id_ in seen: cause = "duplicate item" else: cause = "id not found" prints( "Purging broken playlist item:", id_, "from playlist:", pl.get("title", None), "because:", cause, ) item.getparent().remove(item) continue seen.add(id_)
def create_plugin_action(plugin, tool, for_toolbar, actions=None, toolbar_actions=None, plugin_menu_actions=None): try: ac = tool.create_action(for_toolbar=for_toolbar) if ac is None: raise RuntimeError('create_action() failed to return an action') except Exception: prints('Failed to create action for tool:', tool.name) import traceback traceback.print_exc() return sid = plugin_action_sid(plugin, tool, for_toolbar) if actions is not None and sid in actions: prints('The %s tool from the %s plugin has a non unique name, ignoring' % (tool.name, plugin.name)) else: if actions is not None: actions[sid] = ac ac.sid = sid if for_toolbar: if toolbar_actions is not None: toolbar_actions[sid] = ac plugin_toolbar_actions.append(ac) ac.popup_mode = {'instant':QToolButton.InstantPopup, 'button':QToolButton.MenuButtonPopup}.get( tool.toolbar_button_popup_mode, QToolButton.DelayedPopup) else: if plugin_menu_actions is not None: plugin_menu_actions.append(ac) return ac
def read_available_plugins(raise_error=False): import json, bz2 display_plugins = [] try: raw = get_https_resource_securely(INDEX_URL) if not raw: return raw = json.loads(bz2.decompress(raw)) except: if raise_error: raise traceback.print_exc() return for plugin in itervalues(raw): try: display_plugin = DisplayPlugin(plugin) get_installed_plugin_status(display_plugin) display_plugins.append(display_plugin) except: if DEBUG: prints('======= Plugin Parse Error =======') traceback.print_exc() import pprint pprint.pprint(plugin) display_plugins = sorted(display_plugins, key=lambda k: k.name) return display_plugins
def init_qt(args): parser = option_parser() opts, args = parser.parse_args(args) find_portable_library() if opts.with_library is not None: libpath = os.path.expanduser(opts.with_library) if not os.path.exists(libpath): os.makedirs(libpath) if os.path.isdir(libpath): prefs.set('library_path', os.path.abspath(libpath)) prints('Using library at', prefs['library_path']) override = 'calibre-gui' if islinux else None app = Application(args, override_program_name=override) app.file_event_hook = EventAccumulator() try: is_x11 = app.platformName() == 'xcb' except Exception: import traceback traceback.print_exc() is_x11 = False # Ancient broken VNC servers cannot handle icons of size greater than 256 # https://www.mobileread.com/forums/showthread.php?t=278447 ic = 'lt.png' if is_x11 else 'library.png' app.setWindowIcon(QIcon(I(ic, allow_user_override=False))) return app, opts, args
def report_save_error(self, tb): if self.doing_terminal_save: prints(tb, file=sys.stderr) return error_dialog(self.gui, _('Could not save'), _('Saving of the book failed. Click "Show Details"' ' for more information.'), det_msg=tb, show=True)
def _update_drive_info(self, storage, location_code, name=None): from calibre.utils.date import isoformat, now from calibre.utils.config import from_json, to_json import uuid f = storage.find_path(self.calibre_file_paths['driveinfo'].split('/')) dinfo = {} if f is not None: try: stream = self.get_mtp_file(f) dinfo = json.load(stream, object_hook=from_json) except: prints('Failed to load existing driveinfo.calibre file, with error:') traceback.print_exc() dinfo = {} if dinfo.get('device_store_uuid', None) is None: dinfo['device_store_uuid'] = unicode(uuid.uuid4()) if dinfo.get('device_name', None) is None: dinfo['device_name'] = self.current_friendly_name if name is not None: dinfo['device_name'] = name dinfo['location_code'] = location_code dinfo['last_library_uuid'] = getattr(self, 'current_library_uuid', None) dinfo['calibre_version'] = '.'.join([unicode(i) for i in numeric_version]) dinfo['date_last_connected'] = isoformat(now()) dinfo['mtp_prefix'] = storage.storage_prefix raw = json.dumps(dinfo, default=to_json) self.put_calibre_file(storage, 'driveinfo', BytesIO(raw), len(raw)) self.driveinfo[location_code] = dinfo
def add_action(name, bar): if name is None: bar.addSeparator() return try: ac = actions[name] except KeyError: if DEBUG: prints('Unknown editor tool: %r' % name) return bar.addAction(ac) if name == 'insert-tag': w = bar.widgetForAction(ac) w.setPopupMode(QToolButton.MenuButtonPopup) w.setMenu(self.insert_tag_menu) w.setContextMenuPolicy(Qt.CustomContextMenu) w.customContextMenuRequested.connect(w.showMenu) self._build_insert_tag_button_menu() elif name == 'change-paragraph': m = ac.m = QMenu() ac.setMenu(m) ch = bar.widgetForAction(ac) ch.setPopupMode(QToolButton.InstantPopup) for name in tuple('h%d' % d for d in range(1, 7)) + ('p',): m.addAction(actions['rename-block-tag-%s' % name])
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 replacements.iteritems(): self.cache_file[name] = src
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 self.cache_file.iteritems(): 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 search(self, query, max_results=10, timeout=60): search_url = u'http://robot.litres.ru/pages/catalit_browser/?checkpoint=2000-01-02&'\ 'search=%s&limit=0,%s' search_url = search_url % (urllib2.quote(query), max_results) counter = max_results br = browser() br.addheaders.append(['Accept-Encoding','gzip']) with closing(br.open(search_url, timeout=timeout)) as r: ungzipResponse(r,br) raw= xml_to_unicode(r.read(), strip_encoding_pats=True, assume_utf8=True)[0] parser = etree.XMLParser(recover=True, no_network=True) doc = etree.fromstring(raw, parser=parser) for data in doc.xpath('//*[local-name() = "fb2-book"]'): if counter <= 0: break counter -= 1 try: sRes = self.create_search_result(data) except Exception as e: prints('ERROR: cannot parse search result #%s: %s'%(max_results - counter + 1, e)) continue yield sRes
def start_gui(self, db): from calibre.gui2.ui import Main main = self.main = Main(self.opts, gui_debug=self.gui_debug) if self.splash_screen is not None: self.splash_screen.show_message(_('Initializing user interface...')) try: with gprefs: # Only write gui.json after initialization is complete main.initialize(self.library_path, db, self.listener, self.actions) finally: if self.splash_screen is not None: self.splash_screen.finish(main) self.splash_screen = None if DEBUG: prints('Started up in %.2f seconds'%(time.time() - self.startup_time), 'with', len(db.data), 'books') add_filesystem_book = partial(main.iactions['Add Books'].add_filesystem_book, allow_device=False) main.set_exception_handler() if len(self.args) > 1: files = [os.path.abspath(p) for p in self.args[1:] if not os.path.isdir(p)] if len(files) < len(sys.argv[1:]): prints('Ignoring directories passed as command line arguments') if files: add_filesystem_book(files) for event in self.app.file_event_hook.events: add_filesystem_book(event) self.app.file_event_hook = add_filesystem_book
def render_html_svg_workaround(path_to_html, log, width=590, height=750): from calibre.ebooks.oeb.base import SVG_NS raw = open(path_to_html, 'rb').read() data = None if SVG_NS in raw: try: data = extract_cover_from_embedded_svg(raw, os.path.dirname(path_to_html), log) except: pass if data is None: try: data = extract_calibre_cover(raw, os.path.dirname(path_to_html), log) except: pass if data is None: from calibre.gui2 import is_ok_to_use_qt if is_ok_to_use_qt(): data = render_html_data(path_to_html, width, height) else: from calibre.utils.ipc.simple_worker import fork_job, WorkerError try: result = fork_job('calibre.ebooks', 'render_html_data', (path_to_html, width, height), no_output=True) data = result['result'] except WorkerError as err: prints(err.orig_tb) except: traceback.print_exc() return data
def can_handle_windows(self, usbdevice, debug=False): from calibre.devices.interface import DevicePlugin if self.can_handle.im_func is DevicePlugin.can_handle.im_func: # No custom can_handle implementation return True # Delegate to the unix can_handle function, creating a unix like # USBDevice object from calibre.devices.winusb import get_usb_info dev = usb_info_cache.get(usbdevice) if dev is None: try: data = get_usb_info(usbdevice, debug=debug) except Exception: time.sleep(0.1) try: data = get_usb_info(usbdevice, debug=debug) except Exception: data = {} dev = usb_info_cache[usbdevice] = namedtuple( 'USBDevice', 'vendor_id product_id bcd manufacturer product serial')( usbdevice.vendor_id, usbdevice.product_id, usbdevice.bcd, data.get('manufacturer') or '', data.get('product') or '', data.get('serial_number') or '') if debug: prints('USB Info for device: {}'.format(dev)) return self.can_handle(dev, debug=debug)
def ensure_single_instance(args, open_at): try: from calibre.utils.lock import singleinstance si = singleinstance(singleinstance_name) except Exception: import traceback error_dialog(None, _('Cannot start viewer'), _( 'Failed to start viewer, could not insure only a single instance of the viewer is running. Click "Show Details" for more information'), det_msg=traceback.format_exc(), show=True) raise SystemExit(1) if not si: if len(args) > 1: t = RC(print_error=True, socket_address=viewer_socket_address()) t.start() t.join(3.0) if t.is_alive() or t.conn is None: error_dialog(None, _('Connect to viewer failed'), _( 'Unable to connect to existing viewer window, try restarting the viewer.'), show=True) raise SystemExit(1) t.conn.send((os.path.abspath(args[1]), open_at)) t.conn.close() prints('Opened book in existing viewer instance') raise SystemExit(0) listener = create_listener() return listener
def find_pages(dir, sort_on_mtime=False, verbose=False): ''' Find valid comic pages in a previously un-archived comic. :param dir: Directory in which extracted comic lives :param sort_on_mtime: If True sort pages based on their last modified time. Otherwise, sort alphabetically. ''' extensions = {'jpeg', 'jpg', 'gif', 'png', 'webp'} pages = [] for datum in os.walk(dir): for name in datum[-1]: path = os.path.abspath(os.path.join(datum[0], name)) if '__MACOSX' in path: continue for ext in extensions: if path.lower().endswith('.'+ext): pages.append(path) break sep_counts = {x.replace(os.sep, '/').count('/') for x in pages} # Use the full path to sort unless the files are in folders of different # levels, in which case simply use the filenames. basename = os.path.basename if len(sep_counts) > 1 else lambda x: x if sort_on_mtime: key = lambda x:os.stat(x).st_mtime else: key = lambda x:numeric_sort_key(basename(x)) pages.sort(key=key) if verbose: prints('Found comic pages...') prints('\t'+'\n\t'.join([os.path.relpath(p, dir) for p in pages])) return pages
def custom_columns_in_meta(self): lines = {} for data in self.custom_column_label_map.values(): table, lt = self.custom_table_names(data['num']) if data['normalized']: query = '%s.value' if data['is_multiple']: # query = 'group_concat(%s.value, "{0}")'.format( # data['multiple_seps']['cache_to_list']) # if not display.get('sort_alpha', False): if data['multiple_seps']['cache_to_list'] == '|': query = 'sortconcat_bar(link.id, %s.value)' elif data['multiple_seps']['cache_to_list'] == '&': query = 'sortconcat_amper(link.id, %s.value)' else: prints('WARNING: unknown value in multiple_seps', data['multiple_seps']['cache_to_list']) query = 'sortconcat_bar(link.id, %s.value)' line = '''(SELECT {query} FROM {lt} AS link INNER JOIN {table} ON(link.value={table}.id) WHERE link.book=books.id) custom_{num} '''.format(query=query%table, lt=lt, table=table, num=data['num']) if data['datatype'] == 'series': line += ''',(SELECT extra FROM {lt} WHERE {lt}.book=books.id) custom_index_{num}'''.format(lt=lt, num=data['num']) else: line = ''' (SELECT value FROM {table} WHERE book=books.id) custom_{num} '''.format(table=table, num=data['num']) lines[data['num']] = line return lines
def update_metadata(mi, fmt, stream, plugboards, cdata, error_report=None, plugboard_cache=None): from calibre.ebooks.metadata.meta import set_metadata if error_report is not None: def report_error(mi, fmt, tb): error_report(fmt, tb) try: if plugboard_cache is not None: cpb = plugboard_cache[fmt] else: cpb = find_plugboard(plugboard_save_to_disk_value, fmt, plugboards) if cpb: newmi = mi.deepcopy_metadata() newmi.template_to_attribute(mi, cpb) else: newmi = mi if cdata: newmi.cover_data = ('jpg', cdata) set_metadata(stream, newmi, fmt, report_error=None if error_report is None else report_error) except: if error_report is None: prints('Failed to set metadata for the', fmt, 'format of', mi.title) traceback.print_exc() else: error_report(fmt, traceback.format_exc())
def _upload_cover(self, path, filename, metadata, filepath): if metadata.thumbnail and metadata.thumbnail[-1]: path = path.replace('/', os.sep) is_main = path.startswith(self._main_prefix) thumbnail_dir = MEDIA_THUMBNAIL if is_main else CACHE_THUMBNAIL prefix = None if is_main: prefix = self._main_prefix else: if self._card_a_prefix and \ path.startswith(self._card_a_prefix): prefix = self._card_a_prefix elif self._card_b_prefix and \ path.startswith(self._card_b_prefix): prefix = self._card_b_prefix if prefix is None: prints('WARNING: Failed to find prefix for:', filepath) return thumbnail_dir = os.path.join(prefix, *thumbnail_dir.split('/')) relpath = os.path.relpath(filepath, prefix) if relpath.startswith('..\\'): relpath = relpath[3:] thumbnail_dir = os.path.join(thumbnail_dir, relpath) if not os.path.exists(thumbnail_dir): os.makedirs(thumbnail_dir) cpath = os.path.join(thumbnail_dir, 'main_thumbnail.jpg') with lopen(cpath, 'wb') as f: f.write(metadata.thumbnail[-1]) debug_print('Cover uploaded to: %r'%cpath)
def unicode_listdir(root): root = root.encode(filesystem_encoding) for x in os.listdir(root): try: yield x.decode(filesystem_encoding) except UnicodeDecodeError: prints('Ignoring un-decodable file:', x)
def remove_user(): un = get_valid_user() if get_input((_('Are you sure you want to remove the user %s?') % un) + ' [y/n]:') != 'y': raise SystemExit(0) m.remove_user(un) prints(_('User %s successfully removed!') % un)
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 = cPickle.loads(unhexlify(os.environ['CALIBRE_WORKER_ADDRESS'])) key = unhexlify(os.environ['CALIBRE_WORKER_KEY']) resultf = unhexlify(os.environ['CALIBRE_WORKER_RESULT']).decode('utf-8') 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)): cPickle.dump(result, open(resultf, 'wb'), -1) 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 command_list(args, dbpath): pre = get_parser('') pargs = [x for x in args if x.startswith('--with-library') or x.startswith('--library-path') or not x.startswith('-')] opts = pre.parse_args(sys.argv[:1] + pargs)[0] db = get_db(dbpath, opts) parser = list_option_parser(db=db) opts, args = parser.parse_args(sys.argv[:1] + args) afields = set(FIELDS) if db is not None: for f, data in db.custom_column_label_map.iteritems(): afields.add('*'+f) if data['datatype'] == 'series': afields.add('*'+f+'_index') fields = [str(f.strip().lower()) for f in opts.fields.split(',')] if 'all' in fields: fields = sorted(list(afields)) if not set(fields).issubset(afields): parser.print_help() print prints(_('Invalid fields. Available fields:'), ','.join(sorted(afields)), file=sys.stderr) return 1 if not opts.sort_by in afields and opts.sort_by is not None: parser.print_help() print prints(_('Invalid sort field. Available fields:'), ','.join(afields), file=sys.stderr) return 1 print do_list(db, fields, afields, opts.sort_by, opts.ascending, opts.search, opts.line_width, opts.separator, opts.prefix) return 0
def warning(self, *args, **kwargs): print '\n'+'_'*20, 'WARNING','_'*20 prints(*args, **kwargs) print '_'*50 print ('\n') self.warnings.append((args, kwargs)) sys.stdout.flush()
def test(mi): mt = mi.title.lower() if (exact and mt == title) or \ (not exact and title in mt): return True prints('Title test failed. Expected: \'%s\' found \'%s\''%(title, mt)) return False
def __call__(self, book_id, mi, ok): if mi is True: self.total = book_id else: self.count += 1 prints(u'%.1f%% %s - %s'%((self.count*100)/float(self.total), book_id, mi.title))
def get_valid(prompt, invalidq=lambda x: None): while True: ans = get_input(prompt + ':').strip() fail_message = invalidq(ans) if fail_message is None: return ans prints(fail_message)
def find_pages(dir, sort_on_mtime=False, verbose=False): ''' Find valid comic pages in a previously un-archived comic. :param dir: Directory in which extracted comic lives :param sort_on_mtime: If True sort pages based on their last modified time. Otherwise, sort alphabetically. ''' extensions = ['jpeg', 'jpg', 'gif', 'png'] pages = [] for datum in os.walk(dir): for name in datum[-1]: path = os.path.join(datum[0], name) if '__MACOSX' in path: continue for ext in extensions: if path.lower().endswith('.'+ext): pages.append(path) break if sort_on_mtime: comparator = lambda x, y : cmp(os.stat(x).st_mtime, os.stat(y).st_mtime) else: comparator = lambda x, y : cmp(os.path.basename(x), os.path.basename(y)) pages.sort(cmp=comparator) if verbose: prints('Found comic pages...') prints('\t'+'\n\t'.join([os.path.basename(p) for p in pages])) return pages
def add_book(self, mi, cover_path, paths): if DEBUG: st = time.time() try: cdata = None if cover_path: with open(cover_path, 'rb') as f: cdata = f.read() try: os.remove(cover_path) except Exception: pass book_id = self.dbref().create_book_entry(mi, cover=cdata) self.added_book_ids.add(book_id) except Exception: a = self.report.append a(''), a('-' * 70) a(_('Failed to add the book: ') + mi.title) [a('\t' + f) for f in paths] a(_('With error:')), a(traceback.format_exc()) return self.add_formats(book_id, paths, mi) try: if self.add_formats_to_existing: self.db.update_data_for_find_identical_books(book_id, self.find_identical_books_data) else: self.added_duplicate_info.add(icu_lower(mi.title or _('Unknown'))) except Exception: # Ignore this exception since all it means is that duplicate # detection/automerge will fail for this book. traceback.print_exc() if DEBUG: prints('Added', mi.title, 'to db in: %.1f' % (time.time() - st))
def initialize(self, library_path, db, listener, actions, show_gui=True): opts = self.opts self.preferences_action, self.quit_action = actions self.library_path = library_path self.content_server = None self._spare_pool = None self.must_restart_before_config = False self.listener = Listener(listener) self.check_messages_timer = QTimer() self.check_messages_timer.timeout.connect( self.another_instance_wants_to_talk) self.check_messages_timer.start(1000) for ac in self.iactions.values(): try: ac.do_genesis() except Exception: # Ignore errors in third party plugins import traceback traceback.print_exc() if getattr(ac, 'plugin_path', None) is None: raise self.donate_action = QAction(QIcon(I('donate.png')), _('&Donate to support calibre'), self) for st in self.istores.values(): st.do_genesis() MainWindowMixin.init_main_window_mixin(self, db) # Jobs Button {{{ self.job_manager = JobManager() self.jobs_dialog = JobsDialog(self, self.job_manager) self.jobs_button = JobsButton(horizontal=True, parent=self) self.jobs_button.initialize(self.jobs_dialog, self.job_manager) # }}} LayoutMixin.init_layout_mixin(self) DeviceMixin.init_device_mixin(self) self.progress_indicator = ProgressIndicator(self) self.progress_indicator.pos = (0, 20) self.verbose = opts.verbose self.get_metadata = GetMetadata() self.upload_memory = {} self.metadata_dialogs = [] self.default_thumbnail = None self.tb_wrapper = textwrap.TextWrapper(width=40) self.viewers = collections.deque() self.system_tray_icon = None if config['systray_icon']: self.system_tray_icon = factory( app_id='com.calibre-ebook.gui').create_system_tray_icon( parent=self, title='calibre') if self.system_tray_icon is not None: self.system_tray_icon.setIcon( QIcon(I('lt.png', allow_user_override=False))) if not (iswindows or isosx): self.system_tray_icon.setIcon( QIcon.fromTheme('calibre-gui', self.system_tray_icon.icon())) self.system_tray_icon.setToolTip(self.jobs_button.tray_tooltip()) self.system_tray_icon.setVisible(True) self.jobs_button.tray_tooltip_updated.connect( self.system_tray_icon.setToolTip) elif config['systray_icon']: prints( 'Failed to create system tray icon, your desktop environment probably does not support the StatusNotifier spec' ) self.system_tray_menu = QMenu(self) self.toggle_to_tray_action = self.system_tray_menu.addAction( QIcon(I('page.png')), '') self.toggle_to_tray_action.triggered.connect( self.system_tray_icon_activated) self.system_tray_menu.addAction(self.donate_action) self.donate_button.clicked.connect(self.donate_action.trigger) self.donate_button.setToolTip(self.donate_action.text().replace( '&', '')) self.donate_button.setIcon(self.donate_action.icon()) self.donate_button.setStatusTip(self.donate_button.toolTip()) self.eject_action = self.system_tray_menu.addAction( QIcon(I('eject.png')), _('&Eject connected device')) self.eject_action.setEnabled(False) self.addAction(self.quit_action) self.system_tray_menu.addAction(self.quit_action) self.keyboard.register_shortcut('quit calibre', _('Quit calibre'), default_keys=('Ctrl+Q', ), action=self.quit_action) if self.system_tray_icon is not None: self.system_tray_icon.setContextMenu(self.system_tray_menu) self.system_tray_icon.activated.connect( self.system_tray_icon_activated) self.quit_action.triggered[bool].connect(self.quit) self.donate_action.triggered[bool].connect(self.donate) self.minimize_action = QAction(_('Minimize the calibre window'), self) self.addAction(self.minimize_action) self.keyboard.register_shortcut('minimize calibre', self.minimize_action.text(), default_keys=(), action=self.minimize_action) self.minimize_action.triggered.connect(self.showMinimized) self.esc_action = QAction(self) self.addAction(self.esc_action) self.keyboard.register_shortcut('clear current search', _('Clear the current search'), default_keys=('Esc', ), action=self.esc_action) self.esc_action.triggered.connect(self.esc) self.shift_esc_action = QAction(self) self.addAction(self.shift_esc_action) self.keyboard.register_shortcut('focus book list', _('Focus the book list'), default_keys=('Shift+Esc', ), action=self.shift_esc_action) self.shift_esc_action.triggered.connect(self.shift_esc) self.ctrl_esc_action = QAction(self) self.addAction(self.ctrl_esc_action) self.keyboard.register_shortcut('clear virtual library', _('Clear the virtual library'), default_keys=('Ctrl+Esc', ), action=self.ctrl_esc_action) self.ctrl_esc_action.triggered.connect(self.ctrl_esc) self.alt_esc_action = QAction(self) self.addAction(self.alt_esc_action) self.keyboard.register_shortcut('clear additional restriction', _('Clear the additional restriction'), default_keys=('Alt+Esc', ), action=self.alt_esc_action) self.alt_esc_action.triggered.connect( self.clear_additional_restriction) # ###################### Start spare job server ######################## QTimer.singleShot(1000, self.create_spare_pool) # ###################### Location Manager ######################## self.location_manager.location_selected.connect(self.location_selected) self.location_manager.unmount_device.connect( self.device_manager.umount_device) self.location_manager.configure_device.connect( self.configure_connected_device) self.location_manager.update_device_metadata.connect( self.update_metadata_on_device) self.eject_action.triggered.connect(self.device_manager.umount_device) # ################### Update notification ################### UpdateMixin.init_update_mixin(self, opts) # ###################### Search boxes ######################## SearchRestrictionMixin.init_search_restriction_mixin(self) SavedSearchBoxMixin.init_saved_seach_box_mixin(self) # ###################### Library view ######################## LibraryViewMixin.init_library_view_mixin(self, db) SearchBoxMixin.init_search_box_mixin(self) # Requires current_db self.library_view.model().count_changed_signal.connect( self.iactions['Choose Library'].count_changed) if not gprefs.get('quick_start_guide_added', False): try: add_quick_start_guide(self.library_view) except: import traceback traceback.print_exc() for view in ('library', 'memory', 'card_a', 'card_b'): v = getattr(self, '%s_view' % view) v.selectionModel().selectionChanged.connect(self.update_status_bar) v.model().count_changed_signal.connect(self.update_status_bar) self.library_view.model().count_changed() self.bars_manager.database_changed(self.library_view.model().db) self.library_view.model().database_changed.connect( self.bars_manager.database_changed, type=Qt.QueuedConnection) # ########################## Tags Browser ############################## TagBrowserMixin.init_tag_browser_mixin(self, db) self.library_view.model().database_changed.connect( self.populate_tb_manage_menu, type=Qt.QueuedConnection) # ######################## Search Restriction ########################## if db.prefs['virtual_lib_on_startup']: self.apply_virtual_library(db.prefs['virtual_lib_on_startup']) self.rebuild_vl_tabs() # ########################## Cover Flow ################################ CoverFlowMixin.init_cover_flow_mixin(self) self._calculated_available_height = min(max_available_height() - 15, self.height()) self.resize(self.width(), self._calculated_available_height) self.build_context_menus() for ac in self.iactions.values(): try: ac.gui_layout_complete() except: import traceback traceback.print_exc() if ac.plugin_path is None: raise if config['autolaunch_server']: self.start_content_server() self.read_settings() self.finalize_layout() if self.bars_manager.showing_donate: self.donate_button.start_animation() self.set_window_title() for ac in self.iactions.values(): try: ac.initialization_complete() except: import traceback traceback.print_exc() if ac.plugin_path is None: raise self.set_current_library_information(current_library_name(), db.library_id, db.field_metadata) register_keyboard_shortcuts() self.keyboard.finalize() if show_gui: # Note this has to come after restoreGeometry() because of # https://bugreports.qt.io/browse/QTBUG-56831 self.show() if self.system_tray_icon is not None and self.system_tray_icon.isVisible( ) and opts.start_in_tray: self.hide_windows() self.auto_adder = AutoAdder(gprefs['auto_add_path'], self) self.save_layout_state() # Collect cycles now gc.collect() QApplication.instance().shutdown_signal_received.connect(self.quit) if show_gui and self.gui_debug is not None: QTimer.singleShot(10, self.show_gui_debug_msg) self.iactions['Connect Share'].check_smartdevice_menus() QTimer.singleShot(1, self.start_smartdevice) QTimer.singleShot(100, self.update_toggle_to_tray_action)
def get_metadata(stream, cover=True): with TemporaryDirectory('_pdf_metadata_read') as pdfpath: stream.seek(0) with open(os.path.join(pdfpath, 'src.pdf'), 'wb') as f: shutil.copyfileobj(stream, f) try: res = fork_job('calibre.ebooks.metadata.pdf', 'read_info', (pdfpath, bool(cover))) except WorkerError as e: prints(e.orig_tb) raise RuntimeError('Failed to run pdfinfo') info = res['result'] with open(res['stdout_stderr'], 'rb') as f: raw = f.read().strip() if raw: prints(raw) if info is None: raise ValueError('Could not read info dict from PDF') covpath = os.path.join(pdfpath, 'cover.jpg') cdata = None if cover and os.path.exists(covpath): with open(covpath, 'rb') as f: cdata = f.read() title = info.get('Title', None) or _('Unknown') au = info.get('Author', None) if au is None: au = [_('Unknown')] else: au = string_to_authors(au) mi = MetaInformation(title, au) # if isbn is not None: # mi.isbn = isbn creator = info.get('Creator', None) if creator: mi.book_producer = creator keywords = info.get('Keywords', None) mi.tags = [] if keywords: mi.tags = [x.strip() for x in keywords.split(',')] isbn = [check_isbn(x) for x in mi.tags if check_isbn(x)] if isbn: mi.isbn = isbn = isbn[0] mi.tags = [x for x in mi.tags if check_isbn(x) != isbn] subject = info.get('Subject', None) if subject: mi.tags.insert(0, subject) if 'xmp_metadata' in info: from calibre.ebooks.metadata.xmp import consolidate_metadata mi = consolidate_metadata(mi, info) # Look for recognizable identifiers in the info dict, if they were not # found in the XMP metadata for scheme, check_func in {'doi':check_doi, 'isbn':check_isbn}.iteritems(): if scheme not in mi.get_identifiers(): for k, v in info.iteritems(): if k != 'xmp_metadata': val = check_func(v) if val: mi.set_identifier(scheme, val) break if cdata: mi.cover_data = ('jpeg', cdata) return mi
def _install_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog( self, _('Install %s') % display_plugin.name, '<p>' + _('Installing plugins is a <b>security risk</b>. ' 'Plugins can contain a virus/malware. ' 'Only install it if you got it from a trusted source.' ' Are you sure you want to proceed?'), show_copy_button=False): return if display_plugin.uninstall_plugins: uninstall_names = list(display_plugin.uninstall_plugins) if DEBUG: prints('Uninstalling plugin: ', ', '.join(uninstall_names)) for name_to_remove in uninstall_names: self._uninstall_plugin(name_to_remove) plugin_zip_url = display_plugin.zip_url if DEBUG: prints('Downloading plugin ZIP attachment: ', plugin_zip_url) self.gui.status_bar.showMessage( _('Downloading plugin ZIP attachment: %s') % plugin_zip_url) zip_path = self._download_zip(plugin_zip_url) if DEBUG: prints('Installing plugin: ', zip_path) self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path) do_restart = False try: from calibre.customize.ui import config installed_plugins = frozenset(config['plugins']) try: plugin = add_plugin(zip_path) except NameConflict as e: return error_dialog(self.gui, _('Already exists'), unicode_type(e), show=True) # Check for any toolbars to add to. widget = ConfigWidget(self.gui) widget.gui = self.gui widget.check_for_add_to_toolbars(plugin, previously_installed=plugin.name in installed_plugins) self.gui.status_bar.showMessage( _('Plugin installed: %s') % display_plugin.name) d = info_dialog( self.gui, _('Success'), _('Plugin <b>{0}</b> successfully installed under <b>' ' {1} plugins</b>. You may have to restart calibre ' 'for the plugin to take effect.').format( plugin.name, plugin.type), show_copy_button=False) b = d.bb.addButton(_('&Restart calibre now'), d.bb.AcceptRole) b.setIcon(QIcon(I('lt.png'))) d.do_restart = False def rf(): d.do_restart = True b.clicked.connect(rf) d.set_details('') d.exec_() b.clicked.disconnect() do_restart = d.do_restart display_plugin.plugin = plugin # We cannot read the 'actual' version information as the plugin will not be loaded yet display_plugin.installed_version = display_plugin.available_version except: if DEBUG: prints('ERROR occurred while installing plugin: %s' % display_plugin.name) traceback.print_exc() error_dialog( self.gui, _('Install plugin failed'), _('A problem occurred while installing this plugin.' ' This plugin will now be uninstalled.' ' Please post the error message in details below into' ' the forum thread for this plugin and restart calibre.'), det_msg=traceback.format_exc(), show=True) if DEBUG: prints('Due to error now uninstalling plugin: %s' % display_plugin.name) remove_plugin(display_plugin.name) display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria in [ FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE ]: self.model.beginResetModel(), self.model.endResetModel() self._select_and_focus_view() else: self.model.refresh_plugin(display_plugin) self._select_and_focus_view(change_selection=False) if do_restart: self.do_restart = True self.accept()
def run_gui(opts, args, listener, app, gui_debug=None): si = singleinstance('db') if not si: ext = '.exe' if iswindows else '' error_dialog( None, _('Cannot start calibre'), _('Another calibre program that can modify calibre libraries, such as,' ' {} or {} is already running. You must first shut it down, before' ' starting the main calibre program. If you are sure no such' ' program is running, try restarting your computer.').format( 'calibre-server' + ext, 'calibredb' + ext), show=True) return 1 initialize_file_icon_provider() app.load_builtin_fonts(scan_for_fonts=True) if not dynamic.get('welcome_wizard_was_run', False): from calibre.gui2.wizard import wizard wizard().exec_() dynamic.set('welcome_wizard_was_run', True) from calibre.gui2.ui import Main if isosx: actions = tuple(Main.create_application_menubar()) else: actions = tuple(Main.get_menubar_actions()) runner = GuiRunner(opts, args, actions, listener, app, gui_debug=gui_debug) ret = app.exec_() if getattr(runner.main, 'run_wizard_b4_shutdown', False): from calibre.gui2.wizard import wizard wizard().exec_() if getattr(runner.main, 'restart_after_quit', False): e = sys.executable if getattr(sys, 'frozen', False) else sys.argv[0] if getattr(runner.main, 'debug_on_restart', False) or gui_debug is not None: run_in_debug_mode() else: import subprocess if hasattr(sys, 'frameworks_dir'): app = os.path.dirname( os.path.dirname(os.path.realpath(sys.frameworks_dir))) prints('Restarting with:', app) subprocess.Popen('sleep 3s; open ' + shellquote(app), shell=True) else: os.environ[b'CALIBRE_RESTARTING_FROM_GUI'] = b'1' if iswindows and hasattr(winutil, 'prepare_for_restart'): winutil.prepare_for_restart() args = ['-g' ] if os.path.splitext(e)[0].endswith('-debug') else [] prints('Restarting with:', ' '.join([e] + args)) subprocess.Popen([e] + args) else: if iswindows: try: runner.main.system_tray_icon.hide() except: pass if getattr(runner.main, 'gui_debug', None) is not None: e = sys.executable if getattr(sys, 'frozen', False) else sys.argv[0] debugfile = runner.main.gui_debug from calibre.gui2 import open_local_file if iswindows: with open(debugfile, 'r+b') as f: raw = f.read() raw = re.sub(b'(?<!\r)\n', b'\r\n', raw) f.seek(0) f.truncate() f.write(raw) open_local_file(debugfile) return ret
def fail_request(self, rq, fail_code=None): if fail_code is None: fail_code = QWebEngineUrlRequestJob.Error.UrlNotFound rq.fail(fail_code) prints("Blocking FAKE_PROTOCOL request: {}".format(rq.requestUrl().toString()))
def job_exception(self, job, dialog_title=_('Conversion Error'), retry_func=None): if not hasattr(self, '_modeless_dialogs'): self._modeless_dialogs = [] minz = self.is_minimized_to_tray if self.isVisible(): for x in list(self._modeless_dialogs): if not x.isVisible(): self._modeless_dialogs.remove(x) try: if 'calibre.ebooks.DRMError' in job.details: if not minz: from calibre.gui2.dialogs.drm_error import DRMErrorMessage d = DRMErrorMessage( self, _('Cannot convert') + ' ' + job.description.split(':')[-1].partition('(')[-1][:-1]) d.setModal(False) d.show() self._modeless_dialogs.append(d) return if 'calibre.ebooks.oeb.transforms.split.SplitError' in job.details: title = job.description.split(':')[-1].partition('(')[-1][:-1] msg = _('<p><b>Failed to convert: %s') % title msg += '<p>' + _(''' Many older ebook reader devices are incapable of displaying EPUB files that have internal components over a certain size. Therefore, when converting to EPUB, calibre automatically tries to split up the EPUB into smaller sized pieces. For some files that are large undifferentiated blocks of text, this splitting fails. <p>You can <b>work around the problem</b> by either increasing the maximum split size under EPUB Output in the conversion dialog, or by turning on Heuristic Processing, also in the conversion dialog. Note that if you make the maximum split size too large, your ebook reader may have trouble with the EPUB. ''') if not minz: d = error_dialog(self, _('Conversion Failed'), msg, det_msg=job.details) d.setModal(False) d.show() self._modeless_dialogs.append(d) return if 'calibre.ebooks.mobi.reader.mobi6.KFXError:' in job.details: if not minz: title = job.description.split(':')[-1].partition( '(')[-1][:-1] msg = _('<p><b>Failed to convert: %s') % title idx = job.details.index( 'calibre.ebooks.mobi.reader.mobi6.KFXError:') msg += '<p>' + re.sub( r'(https:\S+)', r'<a href="\1">{}</a>'.format( _('here')), job.details[idx:].partition(':')[2].strip()) d = error_dialog(self, _('Conversion Failed'), msg, det_msg=job.details) d.setModal(False) d.show() self._modeless_dialogs.append(d) return if 'calibre.web.feeds.input.RecipeDisabled' in job.details: if not minz: msg = job.details msg = msg[msg. find('calibre.web.feeds.input.RecipeDisabled:'):] msg = msg.partition(':')[-1] d = error_dialog(self, _('Recipe Disabled'), '<p>%s</p>' % msg) d.setModal(False) d.show() self._modeless_dialogs.append(d) return if 'calibre.ebooks.conversion.ConversionUserFeedBack:' in job.details: if not minz: import json payload = job.details.rpartition( 'calibre.ebooks.conversion.ConversionUserFeedBack:' )[-1] payload = json.loads('{' + payload.partition('{')[-1]) d = { 'info': info_dialog, 'warn': warning_dialog, 'error': error_dialog }.get(payload['level'], error_dialog) d = d(self, payload['title'], '<p>%s</p>' % payload['msg'], det_msg=payload['det_msg']) d.setModal(False) d.show() self._modeless_dialogs.append(d) return except: pass if job.killed: return try: prints(job.details, file=sys.stderr) except: pass if not minz: self.job_error_dialog.show_error(dialog_title, _('<b>Failed</b>') + ': ' + unicode(job.description), det_msg=job.details, retry_func=retry_func)
def another_instance_wants_to_talk(self): try: msg = self.listener.queue.get_nowait() except Empty: return if msg.startswith('launched:'): import json try: argv = json.loads(msg[len('launched:'):]) except ValueError: prints('Failed to decode message from other instance: %r' % msg) if DEBUG: error_dialog( self, 'Invalid message', 'Received an invalid message from other calibre instance.' ' Do you have multiple versions of calibre installed?', det_msg='Invalid msg: %r' % msg, show=True) argv = () if isinstance(argv, (list, tuple)) and len(argv) > 1: files = [ os.path.abspath(p) for p in argv[1:] if not os.path.isdir(p) and os.access(p, os.R_OK) ] if files: self.iactions['Add Books'].add_filesystem_book(files) self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self.show_windows() self.raise_() self.activateWindow() elif msg.startswith('refreshdb:'): m = self.library_view.model() m.db.new_api.reload_from_db() m.db.data.refresh(clear_caches=False, do_search=False) m.resort() m.research() self.tags_view.recount() elif msg.startswith('shutdown:'): self.quit(confirm_quit=False) elif msg.startswith('bookedited:'): parts = msg.split(':')[1:] try: book_id, fmt, library_id = parts[:3] book_id = int(book_id) m = self.library_view.model() db = m.db.new_api if m.db.library_id == library_id and db.has_id(book_id): db.format_metadata(book_id, fmt, allow_cache=False, update_db=True) db.update_last_modified((book_id, )) m.refresh_ids((book_id, )) except Exception: import traceback traceback.print_exc() else: print msg
def ls(dev, path, recurse=False, human_readable_size=False, ll=False, cols=0): def col_split(l, cols): # split list l into columns rows = len(l) / cols if len(l) % cols: rows += 1 m = [] for i in range(rows): m.append(l[i::rows]) return m def row_widths( table): # Calculate widths for each column in the row-wise table tcols = len(table[0]) rowwidths = [0 for i in range(tcols)] for row in table: c = 0 for item in row: rowwidths[c] = len( item) if len(item) > rowwidths[c] else rowwidths[c] c += 1 return rowwidths output = PolyglotBytesIO() if path.endswith("/") and len(path) > 1: path = path[:-1] dirs = dev.list(path, recurse) for dir in dirs: if recurse: prints(dir[0] + ":", file=output) lsoutput, lscoloutput = [], [] files = dir[1] maxlen = 0 if ll: # Calculate column width for size column for file in files: size = len(str(file.size)) if human_readable_size: file = FileFormatter(file) size = len(file.human_readable_size) if size > maxlen: maxlen = size for file in files: file = FileFormatter(file) name = file.name if ll else file.isdir_name lsoutput.append(name) lscoloutput.append(name) if ll: size = str(file.size) if human_readable_size: size = file.human_readable_size prints(file.mode_string, ("%" + str(maxlen) + "s") % size, file.modification_time, name, file=output) if not ll and len(lsoutput) > 0: trytable = [] for colwidth in range(MINIMUM_COL_WIDTH, cols): trycols = int(cols / colwidth) trytable = col_split(lsoutput, trycols) works = True for row in trytable: row_break = False for item in row: if len(item) > colwidth - 1: works, row_break = False, True break if row_break: break if works: break rowwidths = row_widths(trytable) trytablecol = col_split(lscoloutput, len(trytable[0])) for r in range(len(trytable)): for c in range(len(trytable[r])): padding = rowwidths[c] - len(trytable[r][c]) prints(trytablecol[r][c], "".ljust(padding), end=' ', file=output) prints(file=output) prints(file=output) listing = output.getvalue().rstrip().decode('utf-8') + "\n" output.close() return listing
def inspect_mobi(path): from calibre.ebooks.mobi.debug.main import inspect_mobi prints('Inspecting:', path) inspect_mobi(path) print()
def handle_commandline_arg(self, arg): if arg: if os.path.isfile(arg) and os.access(arg, os.R_OK): self.load_ebook(arg) else: prints('Cannot read from:', arg, file=sys.stderr)
__docformat__ = 'restructuredtext en' """ Provides abstraction for metadata reading.writing from a variety of ebook formats. """ import os, sys, re from urllib import unquote, quote from urlparse import urlparse from calibre import relpath, guess_type, remove_bracketed_text, prints from calibre.utils.config import tweaks try: _author_pat = re.compile(tweaks['authors_split_regex']) except: prints('Author split regexp:', tweaks['authors_split_regex'], 'is invalid, using default') _author_pat = re.compile(r'(?i),?\s+(and|with)\s+') def string_to_authors(raw): if not raw: return [] raw = raw.replace('&&', u'\uffff') raw = _author_pat.sub('&', raw) authors = [a.strip().replace(u'\uffff', '&') for a in raw.split('&')] return [a for a in authors if a] def authors_to_string(authors): if authors is not None: return ' & '.join([a.replace('&', '&&') for a in authors if a])
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
def main(): with open(sys.argv[-1], 'rb') as f: css = f.read().decode('utf-8') errors = check_css([create_job(sys.argv[-1], css)]) for error in errors: prints(error)
def tweak(ebook_file): ''' Command line interface to the Tweak Book tool ''' fmt = ebook_file.rpartition('.')[-1].lower() exploder, rebuilder = get_tools(fmt) if exploder is None: prints( 'Cannot tweak %s files. Supported formats are: EPUB, HTMLZ, AZW3, MOBI' % fmt.upper(), file=sys.stderr) raise SystemExit(1) with TemporaryDirectory( '_tweak_' + os.path.basename(ebook_file).rpartition('.')[0]) as tdir: try: opf = exploder(ebook_file, tdir, question=ask_cli_question) except WorkerError as e: prints('Failed to unpack', ebook_file) prints(e.orig_tb) raise SystemExit(1) except Error as e: prints(as_unicode(e), file=sys.stderr) raise SystemExit(1) if opf is None: # The question was answered with No return prints('Book extracted to', tdir) prints('Make your tweaks and once you are done,', __appname__, 'will rebuild', ebook_file, 'from', tdir) print() proceed = ask_cli_question('Rebuild ' + ebook_file + '?') if proceed: prints('Rebuilding', ebook_file, 'please wait ...') try: rebuilder(tdir, ebook_file) except WorkerError as e: prints('Failed to rebuild', ebook_file) prints(e.orig_tb) raise SystemExit(1) prints(ebook_file, 'successfully tweaked')
def create_action(self, spec=None, attr='qaction', shortcut_name=None, persist_shortcut=False): if spec is None: spec = self.action_spec text, icon, tooltip, shortcut = spec if icon is not None: action = QAction(QIcon(I(icon)), text, self.gui) else: action = QAction(text, self.gui) if attr == 'qaction': if hasattr(self.action_menu_clone_qaction, 'rstrip'): mt = unicode_type(self.action_menu_clone_qaction) else: mt = action.text() self.menuless_qaction = ma = QAction(action.icon(), mt, self.gui) ma.triggered.connect(action.trigger) for a in ((action, ma) if attr == 'qaction' else (action, )): a.setAutoRepeat(self.auto_repeat) text = tooltip if tooltip else text a.setToolTip(text) a.setStatusTip(text) a.setWhatsThis(text) shortcut_action = action desc = tooltip if tooltip else None if attr == 'qaction': shortcut_action = ma if shortcut is not None: keys = ((shortcut, ) if isinstance(shortcut, string_or_bytes) else tuple(shortcut)) if shortcut_name is None and spec[0]: shortcut_name = unicode_type(spec[0]) if shortcut_name and self.action_spec[0] and not ( attr == 'qaction' and self.popup_type == QToolButton.InstantPopup): try: self.gui.keyboard.register_shortcut( self.unique_name + ' - ' + attr, shortcut_name, default_keys=keys, action=shortcut_action, description=desc, group=self.action_spec[0], persist_shortcut=persist_shortcut) except NameConflict as e: try: prints(unicode_type(e)) except: pass shortcut_action.setShortcuts([ QKeySequence(key, QKeySequence.PortableText) for key in keys ]) else: self.shortcut_action_for_context_menu = shortcut_action if ismacos: # In Qt 5 keyboard shortcuts dont work unless the # action is explicitly added to the main window self.gui.addAction(shortcut_action) if attr is not None: setattr(self, attr, action) if attr == 'qaction' and self.action_add_menu: menu = QMenu() action.setMenu(menu) if self.action_menu_clone_qaction: menu.addAction(self.menuless_qaction) return action
def run_file_dialog( parent=None, title=None, initial_folder=None, filename=None, save_path=None, allow_multiple=False, only_dirs=False, confirm_overwrite=True, save_as=False, no_symlinks=False, file_types=() ): from calibre.gui2 import sanitize_env_vars secret = os.urandom(32).replace(b'\0', b' ') pipename = '\\\\.\\pipe\\%s' % uuid4() data = [serialize_string('PIPENAME', pipename), serialize_secret(secret)] parent = parent or None if parent is not None: data.append(serialize_hwnd(get_hwnd(parent))) if title: data.append(serialize_string('TITLE', title)) if no_symlinks: data.append(serialize_binary('NO_SYMLINKS', no_symlinks)) if save_as: data.append(serialize_binary('SAVE_AS', save_as)) if confirm_overwrite: data.append(serialize_binary('CONFIRM_OVERWRITE', confirm_overwrite)) if save_path is not None: save_path = process_path(save_path) if os.path.exists(save_path): data.append(serialize_string('SAVE_PATH', save_path)) else: if not initial_folder: initial_folder = select_initial_dir(save_path) if not filename: filename = os.path.basename(save_path) else: if allow_multiple: data.append(serialize_binary('MULTISELECT', allow_multiple)) if only_dirs: data.append(serialize_binary('ONLY_DIRS', only_dirs)) if initial_folder is not None: initial_folder = process_path(initial_folder) if os.path.isdir(initial_folder): data.append(serialize_string('FOLDER', initial_folder)) if filename: if isinstance(filename, bytes): filename = filename.decode(filesystem_encoding) data.append(serialize_string('FILENAME', filename)) if only_dirs: file_types = () # file types not allowed for dir only dialogs elif not file_types: file_types = [(_('All files'), ('*',))] if file_types: data.append(serialize_file_types(file_types)) loop = Loop() server = PipeServer(pipename) with sanitize_env_vars(): h = Helper(subprocess.Popen( [HELPER], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE), data, loop.dialog_closed.emit) h.start() loop.exec_(QEventLoop.ExcludeUserInputEvents) def decode(x): x = x or b'' try: x = x.decode('utf-8') except Exception: x = repr(x) return x def get_errors(): return decode(h.stdoutdata) + ' ' + decode(h.stderrdata) from calibre import prints from calibre.constants import DEBUG if DEBUG: prints('stdout+stderr from file dialog helper:', type('')([h.stdoutdata, h.stderrdata])) if h.rc != 0: raise Exception('File dialog failed: ' + get_errors()) server.join(2) if server.is_alive(): raise Exception('Timed out waiting for read from pipe to complete') if server.err_msg: raise Exception(server.err_msg) if not server.data: return () parts = list(filter(None, server.data.split(b'\0'))) if DEBUG: prints('piped data from file dialog helper:', type('')(parts)) if len(parts) < 2: return () if parts[0] != secret: raise Exception('File dialog failed, incorrect secret received: ' + get_errors()) ans = tuple((os.path.abspath(x.decode('utf-8')) for x in parts[1:])) return ans
def javaScriptConsoleMessage(self, level, msg, linenumber, source_id): prints('%s:%s: %s' % (source_id, linenumber, msg))
def auto_add(self): from calibre.utils.ipc.simple_worker import fork_job, WorkerError from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ebooks.metadata.meta import metadata_from_filename files = [x for x in os.listdir(self.path) if # Must not be in the process of being added to the db x not in self.staging and # Firefox creates 0 byte placeholder files when downloading os.stat(os.path.join(self.path, x)).st_size > 0 and # Must be a file os.path.isfile(os.path.join(self.path, x)) and # Must have read and write permissions os.access(os.path.join(self.path, x), os.R_OK|os.W_OK) and # Must be a known ebook file type self.is_filename_allowed(x) ] data = [] # Give any in progress copies time to complete time.sleep(2) def safe_mtime(x): try: return os.path.getmtime(os.path.join(self.path, x)) except EnvironmentError: return time.time() for fname in sorted(files, key=safe_mtime): f = os.path.join(self.path, fname) # Try opening the file for reading, if the OS prevents us, then at # least on windows, it means the file is open in another # application for writing. We will get notified by # QFileSystemWatcher when writing is completed, so ignore for now. try: open(f, 'rb').close() except: continue tdir = tempfile.mkdtemp(dir=self.tdir) try: fork_job('calibre.ebooks.metadata.meta', 'forked_read_metadata', (f, tdir), no_output=True) except WorkerError as e: prints('Failed to read metadata from:', fname) prints(e.orig_tb) except: import traceback traceback.print_exc() # Ensure that the pre-metadata file size is present. If it isn't, # write 0 so that the file is rescanned szpath = os.path.join(tdir, 'size.txt') try: with open(szpath, 'rb') as f: int(f.read()) except: with open(szpath, 'wb') as f: f.write(b'0') opfpath = os.path.join(tdir, 'metadata.opf') try: if os.stat(opfpath).st_size < 30: raise Exception('metadata reading failed') except: mi = metadata_from_filename(fname) with open(opfpath, 'wb') as f: f.write(metadata_to_opf(mi)) self.staging.add(fname) data.append((fname, tdir)) if data: self.callback(data)
def handle_changes_from_server(self, library_path, change_event): if DEBUG: prints('Received server change event: {} for {}'.format(change_event, library_path)) if self.library_broker.is_gui_library(library_path): self.server_changes.put((library_path, change_event)) self.server_change_notification_timer.start()
def log(self, *args, **kwargs): kwargs['file'] = sys.stderr prints(*args, **kwargs)
def debug_print(*args): global BASE_TIME if BASE_TIME is None: BASE_TIME = time.time() if DEBUG: prints('DEBUG: %6.1f' % (time.time() - BASE_TIME), *args)
def not_single(c): if len(c) > 1: prints(c, 'is not a single character', file=sys.stderr) raise SystemExit(1)
def javaScriptConsoleMessage(self, msg, lineno, source_id): prints('JSBrowser msg():%s:%s:' % (unicode(source_id), lineno), unicode(msg))
def find_icons(): global icon_data if icon_data is not None: return icon_data base_dirs = [(os.environ.get('XDG_DATA_HOME') or os.path.expanduser('~/.local/share')) + '/icons'] base_dirs += [os.path.expanduser('~/.icons')] base_dirs += [ os.path.join(b, 'icons') for b in os.environ.get( 'XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(os.pathsep) ] + ['/usr/share/pixmaps'] ans = defaultdict(list) sz_pat = re.compile(r'/((?:\d+x\d+)|scalable)/') cache_file = os.path.join(cache_dir(), 'icon-theme-cache.calibre_msgpack') exts = {'.svg', '.png', '.xpm'} def read_icon_theme_dir(dirpath): ans = defaultdict(list) for path in walk(dirpath): bn = os.path.basename(path) name, ext = os.path.splitext(bn) if ext in exts: sz = sz_pat.findall(path) if sz: sz = sz[-1] if sz == 'scalable': sz = 100000 else: sz = int(sz.partition('x')[0]) idx = len(ans[name]) ans[name].append((-sz, idx, sz, path)) for icons in itervalues(ans): icons.sort(key=list) return {k: (-v[0][2], v[0][3]) for k, v in iteritems(ans)} try: with open(cache_file, 'rb') as f: cache = f.read() cache = msgpack_loads(cache) mtimes, cache = defaultdict(int, cache['mtimes']), defaultdict( dict, cache['data']) except Exception: mtimes, cache = defaultdict(int), defaultdict(dict) seen_dirs = set() changed = False for loc in base_dirs: try: subdirs = os.listdir(loc) except EnvironmentError: continue for dname in subdirs: d = os.path.join(loc, dname) if os.path.isdir(d): try: mtime = os.stat(d).st_mtime except EnvironmentError: continue seen_dirs.add(d) if mtime != mtimes[d]: changed = True try: cache[d] = read_icon_theme_dir(d) except Exception: prints( 'Failed to read icon theme dir: %r with error:' % d) import traceback traceback.print_exc() mtimes[d] = mtime for name, data in iteritems(cache[d]): ans[name].append(data) for removed in set(mtimes) - seen_dirs: mtimes.pop(removed), cache.pop(removed) changed = True if changed: data = msgpack_dumps({'data': cache, 'mtimes': mtimes}) try: with open(cache_file, 'wb') as f: f.write(data) except Exception: import traceback traceback.print_exc() for icons in itervalues(ans): icons.sort(key=list) icon_data = {k: v[0][1] for k, v in iteritems(ans)} return icon_data
def main(args=sys.argv): parser = option_parser() opts, args = parser.parse_args(args) if len(args) < 2: parser.print_help() prints(_('No file specified'), file=sys.stderr) return 1 path = args[1] stream_type = os.path.splitext(path)[1].replace('.', '').lower() trying_to_set = False for pref in config().option_set.preferences: if pref.name in ('to_opf', 'get_cover'): continue if getattr(opts, pref.name) is not None: trying_to_set = True break with open(path, 'rb') as stream: mi = get_metadata(stream, stream_type, force_read_metadata=True) if trying_to_set: prints(_('Original metadata')+'::') metadata = unicode_type(mi) if trying_to_set: metadata = '\t'+'\n\t'.join(metadata.split('\n')) prints(metadata, safe_encode=True) if trying_to_set: with open(path, 'r+b') as stream: do_set_metadata(opts, mi, stream, stream_type) stream.seek(0) stream.flush() lrf = None if stream_type == 'lrf': if opts.lrf_bookid is not None: lrf = LRFMetaFile(stream) lrf.book_id = opts.lrf_bookid mi = get_metadata(stream, stream_type, force_read_metadata=True) prints('\n' + _('Changed metadata') + '::') metadata = unicode_type(mi) metadata = '\t'+'\n\t'.join(metadata.split('\n')) prints(metadata, safe_encode=True) if lrf is not None: prints('\tBookID:', lrf.book_id) if opts.to_opf is not None: from calibre.ebooks.metadata.opf2 import OPFCreator opf = OPFCreator(getcwd(), mi) with open(opts.to_opf, 'wb') as f: opf.render(f) prints(_('OPF created in'), opts.to_opf) if opts.get_cover is not None: if mi.cover_data and mi.cover_data[1]: with open(opts.get_cover, 'wb') as f: f.write(mi.cover_data[1]) prints(_('Cover saved to'), f.name) else: prints(_('No cover found'), file=sys.stderr) return 0
root = container.parsed(spine_name) head = root.xpath('//*[local-name()="head"][1]')[0] href = container.name_to_href(name, spine_name) etree.SubElement(head, XHTML('link'), rel='stylesheet', type='text/css', href=href).tail = '\n' container.dirty(spine_name) return True if __name__ == '__main__': from calibre.ebooks.oeb.polish.container import get_container from calibre.ebooks.oeb.polish.stats import StatsCollector from calibre.utils.logging import default_log default_log.filter_level = default_log.DEBUG inbook = sys.argv[-1] ebook = get_container(inbook, default_log) report = [] stats = StatsCollector(ebook, do_embed=True) embed_all_fonts(ebook, stats, report.append) outbook, ext = inbook.rpartition('.')[0::2] outbook += '_subset.' + ext ebook.commit(outbook) prints('\nReport:') for msg in report: prints(msg) print() prints('Output written to:', outbook)
def __init__(self, path): self.handle_map = {} import win32file, winerror from pywintypes import error from collections import defaultdict if isbytestring(path): path = path.decode(filesystem_encoding) if not os.path.exists(path): return names = os.listdir(path) name_to_fileid = { x: windows_get_fileid(os.path.join(path, x)) for x in names } fileid_to_names = defaultdict(set) for name, fileid in iteritems(name_to_fileid): fileid_to_names[fileid].add(name) for x in names: f = os.path.normcase(os.path.abspath(os.path.join(path, x))) if not os.path.isfile(f): continue try: # Ensure the file is not read-only win32file.SetFileAttributes(f, win32file.FILE_ATTRIBUTE_NORMAL) except: pass try: h = win32file.CreateFileW(f, win32file.GENERIC_READ, win32file.FILE_SHARE_DELETE, None, win32file.OPEN_EXISTING, win32file.FILE_FLAG_SEQUENTIAL_SCAN, 0) except error as e: if getattr(e, 'winerror', 0) == winerror.ERROR_SHARING_VIOLATION: # The file could be a hardlink to an already opened file, # in which case we use the same handle for both files fileid = name_to_fileid[x] found = False if fileid is not None: for other in fileid_to_names[fileid]: other = os.path.normcase( os.path.abspath(os.path.join(path, other))) if other in self.handle_map: self.handle_map[f] = self.handle_map[other] found = True break if found: continue self.close_handles() if getattr(e, 'winerror', 0) == winerror.ERROR_SHARING_VIOLATION: err = IOError(errno.EACCES, _('File is open in another process')) err.filename = f raise err prints('CreateFile failed for: %r' % f) raise except: self.close_handles() prints('CreateFile failed for: %r' % f) raise self.handle_map[f] = h
def javaScriptAlert(self, frame, msg): if self.view() is not None: return QWebPage.javaScriptAlert(self, frame, msg) prints('JSBrowser alert():', unicode(msg))
def dump_fonts(self): self.join() for family in self.font_families: prints(family) for font in self.fonts_for_family(family): prints('\t%s: %s'%(font['full_name'], font['path'])) prints(end='\t') for key in ('font-stretch', 'font-weight', 'font-style'): prints('%s: %s'%(key, font[key]), end=' ') prints() prints('\tSub-family:', font['wws_subfamily_name'] or font['preferred_subfamily_name'] or font['subfamily_name']) prints() prints()
def main(args=sys.argv): from calibre.constants import debug debug() opts, args = option_parser().parse_args(args) if opts.gui: from calibre.gui2.main import main print_basic_debug_info() main(['calibre']) elif opts.gui_debug is not None: run_debug_gui(opts.gui_debug) elif opts.show_gui_debug: import time, re time.sleep(1) from calibre.gui2 import open_local_file if iswindows: with open(opts.show_gui_debug, 'r+b') as f: raw = f.read() raw = re.sub('(?<!\r)\n', '\r\n', raw) f.seek(0) f.truncate() f.write(raw) open_local_file(opts.show_gui_debug) elif opts.viewer: from calibre.gui2.viewer.main import main main(['ebook-viewer'] + args[1:]) elif opts.py_console: from calibre.utils.pyconsole.main import main main() 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.gui2.tweak_book.main import main main(['ebook-edit'] + args[1:]) elif opts.explode_book: from calibre.ebooks.tweak import tweak tweak(opts.explode_book) elif opts.test_build: from calibre.test_build import test 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'] + [opts.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 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' }: for path in args[1:]: ext = path.rpartition('.')[-1] if ext == 'docx': 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) else: from calibre import ipython ipython() return 0