def interface_data(ctx, rd): ''' Return the data needed to create the server main UI Optional: ?num=50&sort=timestamp.desc&library_id=<default library> &search=''&extra_books='' ''' ans = { 'username':rd.username, 'output_format':prefs['output_format'].upper(), 'input_formats':{x.upper():True for x in available_input_formats()}, 'gui_pubdate_display_format':tweaks['gui_pubdate_display_format'], 'gui_timestamp_display_format':tweaks['gui_timestamp_display_format'], 'gui_last_modified_display_format':tweaks['gui_last_modified_display_format'], 'use_roman_numerals_for_series_number': get_use_roman(), } ans['library_map'], ans['default_library'] = ctx.library_info(rd) ud = {} if rd.username: # Override session data with stored values for the authenticated user, # if any ud = ctx.user_manager.get_session_data(rd.username) lid = ud.get('library_id') if lid and lid in ans['library_map']: rd.query.set('library_id', lid) usort = ud.get('sort') if usort: rd.query.set('sort', usort) ans['library_id'], db, sorts, orders = get_basic_query_data(ctx, rd) ans['user_session_data'] = ud try: num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS)) except Exception: raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num')) with db.safe_read_lock: try: ans['search_result'] = search_result(ctx, rd, db, rd.query.get('search', ''), num, 0, ','.join(sorts), ','.join(orders)) except ParseException: ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders)) sf = db.field_metadata.ui_sortable_field_keys() sf.pop('ondevice', None) ans['sortable_fields'] = sorted((( sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()), key=lambda (field, name):sort_key(name)) ans['field_metadata'] = db.field_metadata.all_metadata() ans['icon_map'] = icon_map() ans['icon_path'] = ctx.url_for('/icon', which='') mdata = ans['metadata'] = {} try: extra_books = set(int(x) for x in rd.query.get('extra_books', '').split(',')) except Exception: extra_books = () for coll in (ans['search_result']['book_ids'], extra_books): for book_id in coll: if book_id not in mdata: data = book_as_json(db, book_id) if data is not None: mdata[book_id] = data return ans
def open_ebook(self, checked): files = choose_files(self, 'ebook viewer open dialog', _('Choose ebook'), [(_('Ebooks'), available_input_formats())], all_files=False, select_only_single_file=True) if files: self.load_ebook(files[0])
def send_multiple_by_mail(self, recipients, delete_from_library): ids = set(self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()) if not ids: return db = self.current_db db_fmt_map = {book_id: set((db.formats(book_id, index_is_id=True) or "").upper().split(",")) for book_id in ids} ofmts = {x.upper() for x in available_output_formats()} ifmts = {x.upper() for x in available_input_formats()} bad_recipients = {} auto_convert_map = defaultdict(list) for to, fmts, subject in recipients: rfmts = set(fmts) ok_ids = {book_id for book_id, bfmts in db_fmt_map.iteritems() if bfmts.intersection(rfmts)} convert_ids = ids - ok_ids self.send_by_mail(to, fmts, delete_from_library, subject=subject, send_ids=ok_ids, do_auto_convert=False) if not rfmts.intersection(ofmts): bad_recipients[to] = (convert_ids, True) continue outfmt = tuple(f for f in fmts if f in ofmts)[0] ok_ids = {book_id for book_id in convert_ids if db_fmt_map[book_id].intersection(ifmts)} bad_ids = convert_ids - ok_ids if bad_ids: bad_recipients[to] = (bad_ids, False) if ok_ids: auto_convert_map[outfmt].append((to, subject, ok_ids)) if auto_convert_map: titles = {book_id for x in auto_convert_map.itervalues() for data in x for book_id in data[2]} titles = {db.title(book_id, index_is_id=True) for book_id in titles} if self.auto_convert_question( _("Auto convert the following books before sending via email?"), list(titles) ): for ofmt, data in auto_convert_map.iteritems(): ids = {bid for x in data for bid in x[2]} data = [(to, subject) for to, subject, x in data] self.iactions["Convert Books"].auto_convert_multiple_mail(ids, data, ofmt, delete_from_library) if bad_recipients: det_msg = [] titles = {book_id for x in bad_recipients.itervalues() for book_id in x[0]} titles = {book_id: db.title(book_id, index_is_id=True) for book_id in titles} for to, (ids, nooutput) in bad_recipients.iteritems(): msg = ( _("This recipient has no valid formats defined") if nooutput else _("These books have no suitable input formats for conversion") ) det_msg.append("%s - %s" % (to, msg)) det_msg.extend("\t" + titles[bid] for bid in ids) det_msg.append("\n") warning_dialog( self, _("Could not send"), _("Could not send books to some recipients. Click Show Details for more information"), det_msg="\n".join(det_msg), show=True, )
def ask_for_open(self, path=None): if path is None: files = choose_files( self, 'ebook viewer open dialog', _('Choose e-book'), [(_('E-books'), available_input_formats())], all_files=False, select_only_single_file=True) if not files: return path = files[0] self.load_ebook(path)
def open_ebook(self, checked): files = choose_files( self, "ebook viewer open dialog", _("Choose ebook"), [(_("Ebooks"), available_input_formats())], all_files=False, select_only_single_file=True, ) if files: self.load_ebook(files[0])
def send_multiple_by_mail(self, recipients, delete_from_library): ids = set(self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()) if not ids: return db = self.current_db db_fmt_map = {book_id:set((db.formats(book_id, index_is_id=True) or '').upper().split(',')) for book_id in ids} ofmts = {x.upper() for x in available_output_formats()} ifmts = {x.upper() for x in available_input_formats()} bad_recipients = {} auto_convert_map = defaultdict(list) for to, fmts, subject in recipients: rfmts = set(fmts) ok_ids = {book_id for book_id, bfmts in db_fmt_map.iteritems() if bfmts.intersection(rfmts)} convert_ids = ids - ok_ids self.send_by_mail(to, fmts, delete_from_library, subject=subject, send_ids=ok_ids, do_auto_convert=False) if not rfmts.intersection(ofmts): bad_recipients[to] = (convert_ids, True) continue outfmt = tuple(f for f in fmts if f in ofmts)[0] ok_ids = {book_id for book_id in convert_ids if db_fmt_map[book_id].intersection(ifmts)} bad_ids = convert_ids - ok_ids if bad_ids: bad_recipients[to] = (bad_ids, False) if ok_ids: auto_convert_map[outfmt].append((to, subject, ok_ids)) if auto_convert_map: titles = {book_id for x in auto_convert_map.itervalues() for data in x for book_id in data[2]} titles = {db.title(book_id, index_is_id=True) for book_id in titles} if self.auto_convert_question( _('Auto convert the following books before sending via email?'), list(titles)): for ofmt, data in auto_convert_map.iteritems(): ids = {bid for x in data for bid in x[2]} data = [(to, subject) for to, subject, x in data] self.iactions['Convert Books'].auto_convert_multiple_mail(ids, data, ofmt, delete_from_library) if bad_recipients: det_msg = [] titles = {book_id for x in bad_recipients.itervalues() for book_id in x[0]} titles = {book_id:db.title(book_id, index_is_id=True) for book_id in titles} for to, (ids, nooutput) in bad_recipients.iteritems(): msg = _('This recipient has no valid formats defined') if nooutput else \ _('These books have no suitable input formats for conversion') det_msg.append('%s - %s' % (to, msg)) det_msg.extend('\t' + titles[bid] for bid in ids) det_msg.append('\n') warning_dialog(self, _('Could not send'), _('Could not send books to some recipients. Click Show Details for more information'), det_msg='\n'.join(det_msg), show=True)
def basic_interface_data(ctx, rd): ans = { 'username':rd.username, 'output_format':prefs['output_format'].upper(), 'input_formats':{x.upper():True for x in available_input_formats()}, 'gui_pubdate_display_format':tweaks['gui_pubdate_display_format'], 'gui_timestamp_display_format':tweaks['gui_timestamp_display_format'], 'gui_last_modified_display_format':tweaks['gui_last_modified_display_format'], 'use_roman_numerals_for_series_number': get_use_roman(), 'translations': get_translations(), 'allow_console_print':getattr(rd.opts, 'allow_console_print', False), 'icon_map': icon_map(), 'icon_path': ctx.url_for('/icon', which=''), } ans['library_map'], ans['default_library_id'] = ctx.library_info(rd) return ans
def unarchive(self, path, tdir): extract(path, tdir) files = list(walk(tdir)) files = [f if isinstance(f, unicode) else f.decode(filesystem_encoding) for f in files] from calibre.customize.ui import available_input_formats fmts = set(available_input_formats()) fmts -= {'htm', 'html', 'xhtm', 'xhtml'} fmts -= set(ARCHIVE_FMTS) for ext in fmts: for f in files: if f.lower().endswith('.'+ext): if ext in ['txt', 'rtf'] and os.stat(f).st_size < 2048: continue return f, ext return self.find_html_index(files)
def basic_interface_data(ctx, rd): ans = { 'username': rd.username, 'output_format': prefs['output_format'].upper(), 'input_formats': {x.upper(): True for x in available_input_formats()}, 'gui_pubdate_display_format': tweaks['gui_pubdate_display_format'], 'gui_timestamp_display_format': tweaks['gui_timestamp_display_format'], 'gui_last_modified_display_format': tweaks['gui_last_modified_display_format'], 'use_roman_numerals_for_series_number': get_use_roman(), 'translations': get_translations(), 'icon_map': icon_map(), 'icon_path': ctx.url_for('/icon', which=''), 'custom_list_template': getattr(ctx, 'custom_list_template', None) or custom_list_template(), } ans['library_map'], ans['default_library_id'] = ctx.library_info(rd) return ans
def basic_interface_data(ctx, rd): ans = { 'username': rd.username, 'output_format': prefs['output_format'].upper(), 'input_formats': {x.upper(): True for x in available_input_formats()}, 'gui_pubdate_display_format': tweaks['gui_pubdate_display_format'], 'gui_timestamp_display_format': tweaks['gui_timestamp_display_format'], 'gui_last_modified_display_format': tweaks['gui_last_modified_display_format'], 'completion_mode': tweaks['completion_mode'], 'use_roman_numerals_for_series_number': get_use_roman(), 'translations': get_translations(), 'icon_map': icon_map(), 'icon_path': ctx.url_for('/icon', which=''), 'custom_list_template': getattr(ctx, 'custom_list_template', None) or custom_list_template(), 'search_the_net_urls': getattr(ctx, 'search_the_net_urls', None) or [], 'num_per_page': rd.opts.num_per_page, 'donate_link': localize_website_link('https://calibre-ebook.com/donate') } ans['library_map'], ans['default_library_id'] = ctx.library_info(rd) return ans
def basic_interface_data(ctx, rd): ans = { 'username': rd.username, 'output_format': prefs['output_format'].upper(), 'input_formats': {x.upper(): True for x in available_input_formats()}, 'gui_pubdate_display_format': tweaks['gui_pubdate_display_format'], 'gui_timestamp_display_format': tweaks['gui_timestamp_display_format'], 'gui_last_modified_display_format': tweaks['gui_last_modified_display_format'], 'completion_mode': tweaks['completion_mode'], 'use_roman_numerals_for_series_number': get_use_roman(), 'translations': get_translations(), 'icon_map': icon_map(), 'icon_path': ctx.url_for('/icon', which=''), 'custom_list_template': getattr(ctx, 'custom_list_template', None) or custom_list_template(), 'search_the_net_urls': getattr(ctx, 'search_the_net_urls', None) or [], 'num_per_page': rd.opts.num_per_page, 'default_book_list_mode': rd.opts.book_list_mode, 'donate_link': localize_website_link('https://calibre-ebook.com/donate') } ans['library_map'], ans['default_library_id'] = ctx.library_info(rd) return ans
def is_supported(path): ext = os.path.splitext(path)[1].replace('.', '').lower() ext = re.sub(r'(x{0,1})htm(l{0,1})', 'html', ext) return ext in available_input_formats() or ext == 'kepub'
def write_completion(bash_comp_dest, zsh): from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop from calibre.gui2.viewer.main import option_parser as viewer_op from calibre.gui2.tweak_book.main import option_parser as tweak_op from calibre.ebooks.metadata.sources.cli import option_parser as fem_op from calibre.gui2.main import option_parser as guiop from calibre.utils.smtp import option_parser as smtp_op from calibre.srv.standalone import create_option_parser as serv_op from calibre.ebooks.oeb.polish.main import option_parser as polish_op, SUPPORTED from calibre.ebooks.oeb.polish.import_book import IMPORTABLE from calibre.debug import option_parser as debug_op from calibre.ebooks import BOOK_EXTENSIONS from calibre.customize.ui import available_input_formats input_formats = sorted(all_input_formats()) tweak_formats = sorted(x.lower() for x in SUPPORTED|IMPORTABLE) if bash_comp_dest and not os.path.exists(os.path.dirname(bash_comp_dest)): os.makedirs(os.path.dirname(bash_comp_dest)) complete = 'calibre-complete' if getattr(sys, 'frozen_path', None): complete = os.path.join(getattr(sys, 'frozen_path'), complete) with open(bash_comp_dest or os.devnull, 'wb') as f: def o_and_e(*args, **kwargs): f.write(opts_and_exts(*args, **kwargs)) zsh.opts_and_exts(*args, **kwargs) def o_and_w(*args, **kwargs): f.write(opts_and_words(*args, **kwargs)) zsh.opts_and_words(*args, **kwargs) f.write('# calibre Bash Shell Completion\n') o_and_e('calibre', guiop, BOOK_EXTENSIONS) o_and_e('lrf2lrs', lrf2lrsop, ['lrf'], file_map={'--output':['lrs']}) o_and_e('ebook-meta', metaop, list(meta_filetypes()), cover_opts=['--cover', '-c'], opf_opts=['--to-opf', '--from-opf']) o_and_e('ebook-polish', polish_op, [x.lower() for x in SUPPORTED], cover_opts=['--cover', '-c'], opf_opts=['--opf', '-o']) o_and_e('lrfviewer', lrfviewerop, ['lrf']) o_and_e('ebook-viewer', viewer_op, input_formats) o_and_e('ebook-edit', tweak_op, tweak_formats) o_and_w('fetch-ebook-metadata', fem_op, []) o_and_w('calibre-smtp', smtp_op, []) o_and_w('calibre-server', serv_op, []) o_and_e('calibre-debug', debug_op, ['py', 'recipe', 'mobi', 'azw', 'azw3', 'docx'], file_map={ '--tweak-book':['epub', 'azw3', 'mobi'], '--subset-font':['ttf', 'otf'], '--exec-file':['py', 'recipe'], '--add-simple-plugin':['py'], '--inspect-mobi':['mobi', 'azw', 'azw3'], '--viewer':sorted(available_input_formats()), }) f.write(textwrap.dedent(''' _ebook_device_ls() { local pattern search listing prefix pattern="$1" search="$1" if [[ -n "{$pattern}" ]]; then if [[ "${pattern:(-1)}" == "/" ]]; then pattern="" else pattern="$(basename ${pattern} 2> /dev/null)" search="$(dirname ${search} 2> /dev/null)" fi fi if [[ "x${search}" == "x" || "x${search}" == "x." ]]; then search="/" fi listing="$(ebook-device ls ${search} 2>/dev/null)" prefix="${search}" if [[ "x${prefix:(-1)}" != "x/" ]]; then prefix="${prefix}/" fi echo $(compgen -P "${prefix}" -W "${listing}" "${pattern}") } _ebook_device() { local cur prev cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() case "${prev}" in ls|rm|mkdir|touch|cat ) COMPREPLY=( $(_ebook_device_ls "${cur}") ) return 0 ;; cp ) if [[ ${cur} == dev:* ]]; then COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) return 0 else _filedir return 0 fi ;; dev ) COMPREPLY=( $(compgen -W "cp ls rm mkdir touch cat info books df" "${cur}") ) return 0 ;; * ) if [[ ${cur} == dev:* ]]; then COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) return 0 else if [[ ${prev} == dev:* ]]; then _filedir return 0 else COMPREPLY=( $(compgen -W "dev:" "${cur}") ) return 0 fi return 0 fi ;; esac } complete -o nospace -F _ebook_device ebook-device complete -o nospace -C %s ebook-convert ''')%complete) zsh.write()
def __init__(self, input, output, log, report_progress=DummyReporter(), dummy=False, merge_plugin_recs=True, abort_after_input_dump=False, override_input_metadata=False): ''' :param input: Path to input file. :param output: Path to output file/directory ''' if isbytestring(input): input = input.decode(filesystem_encoding) if isbytestring(output): output = output.decode(filesystem_encoding) self.original_input_arg = input self.input = os.path.abspath(input) self.output = os.path.abspath(output) self.log = log self.ui_reporter = report_progress self.abort_after_input_dump = abort_after_input_dump self.override_input_metadata = override_input_metadata # Pipeline options {{{ # Initialize the conversion options that are independent of input and # output formats. The input and output plugins can still disable these # options via recommendations. self.pipeline_options = [ OptionRecommendation(name='verbose', recommended_value=0, level=OptionRecommendation.LOW, short_switch='v', help=_('Level of verbosity. Specify multiple times for greater ' 'verbosity.') ), OptionRecommendation(name='debug_pipeline', recommended_value=None, level=OptionRecommendation.LOW, short_switch='d', help=_('Save the output from different stages of the conversion ' 'pipeline to the specified ' 'directory. Useful if you are unsure at which stage ' 'of the conversion process a bug is occurring.') ), OptionRecommendation(name='input_profile', recommended_value='default', level=OptionRecommendation.LOW, choices=[x.short_name for x in input_profiles()], help=_('Specify the input profile. The input profile gives the ' 'conversion system information on how to interpret ' 'various information in the input document. For ' 'example resolution dependent lengths (i.e. lengths in ' 'pixels). Choices are:')+\ ', '.join([x.short_name for x in input_profiles()]) ), OptionRecommendation(name='output_profile', recommended_value='default', level=OptionRecommendation.LOW, choices=[x.short_name for x in output_profiles()], help=_('Specify the output profile. The output profile ' 'tells the conversion system how to optimize the ' 'created document for the specified device. In some cases, ' 'an output profile is required to produce documents that ' 'will work on a device. For example EPUB on the SONY reader. ' 'Choices are:') + \ ', '.join([x.short_name for x in output_profiles()]) ), OptionRecommendation(name='base_font_size', recommended_value=0, level=OptionRecommendation.LOW, help=_('The base font size in pts. All font sizes in the produced book ' 'will be rescaled based on this size. By choosing a larger ' 'size you can make the fonts in the output bigger and vice ' 'versa. By default, the base font size is chosen based on ' 'the output profile you chose.' ) ), OptionRecommendation(name='font_size_mapping', recommended_value=None, level=OptionRecommendation.LOW, help=_('Mapping from CSS font names to font sizes in pts. ' 'An example setting is 12,12,14,16,18,20,22,24. ' 'These are the mappings for the sizes xx-small to xx-large, ' 'with the final size being for huge fonts. The font ' 'rescaling algorithm uses these sizes to intelligently ' 'rescale fonts. The default is to use a mapping based on ' 'the output profile you chose.' ) ), OptionRecommendation(name='disable_font_rescaling', recommended_value=False, level=OptionRecommendation.LOW, help=_('Disable all rescaling of font sizes.' ) ), OptionRecommendation(name='minimum_line_height', recommended_value=120.0, level=OptionRecommendation.LOW, help=_( 'The minimum line height, as a percentage of the element\'s ' 'calculated font size. calibre will ensure that every element ' 'has a line height of at least this setting, irrespective of ' 'what the input document specifies. Set to zero to disable. ' 'Default is 120%. Use this setting in preference to ' 'the direct line height specification, unless you know what ' 'you are doing. For example, you can achieve "double spaced" ' 'text by setting this to 240.' ) ), OptionRecommendation(name='line_height', recommended_value=0, level=OptionRecommendation.LOW, help=_( 'The line height in pts. Controls spacing between consecutive ' 'lines of text. Only applies to elements that do not define ' 'their own line height. In most cases, the minimum line height ' 'option is more useful. ' 'By default no line height manipulation is performed.' ) ), OptionRecommendation(name='linearize_tables', recommended_value=False, level=OptionRecommendation.LOW, help=_('Some badly designed documents use tables to control the ' 'layout of text on the page. When converted these documents ' 'often have text that runs off the page and other artifacts. ' 'This option will extract the content from the tables and ' 'present it in a linear fashion.' ) ), OptionRecommendation(name='level1_toc', recommended_value=None, level=OptionRecommendation.LOW, help=_('XPath expression that specifies all tags that ' 'should be added to the Table of Contents at level one. If ' 'this is specified, it takes precedence over other forms ' 'of auto-detection.' ' See the XPath Tutorial in the calibre User Manual for examples.' ) ), OptionRecommendation(name='level2_toc', recommended_value=None, level=OptionRecommendation.LOW, help=_('XPath expression that specifies all tags that should be ' 'added to the Table of Contents at level two. Each entry is added ' 'under the previous level one entry.' ' See the XPath Tutorial in the calibre User Manual for examples.' ) ), OptionRecommendation(name='level3_toc', recommended_value=None, level=OptionRecommendation.LOW, help=_('XPath expression that specifies all tags that should be ' 'added to the Table of Contents at level three. Each entry ' 'is added under the previous level two entry.' ' See the XPath Tutorial in the calibre User Manual for examples.' ) ), OptionRecommendation(name='use_auto_toc', recommended_value=False, level=OptionRecommendation.LOW, help=_('Normally, if the source file already has a Table of ' 'Contents, it is used in preference to the auto-generated one. ' 'With this option, the auto-generated one is always used.' ) ), OptionRecommendation(name='no_chapters_in_toc', recommended_value=False, level=OptionRecommendation.LOW, help=_("Don't add auto-detected chapters to the Table of " 'Contents.' ) ), OptionRecommendation(name='toc_threshold', recommended_value=6, level=OptionRecommendation.LOW, help=_( 'If fewer than this number of chapters is detected, then links ' 'are added to the Table of Contents. Default: %default') ), OptionRecommendation(name='max_toc_links', recommended_value=50, level=OptionRecommendation.LOW, help=_('Maximum number of links to insert into the TOC. Set to 0 ' 'to disable. Default is: %default. Links are only added to the ' 'TOC if less than the threshold number of chapters were detected.' ) ), OptionRecommendation(name='toc_filter', recommended_value=None, level=OptionRecommendation.LOW, help=_('Remove entries from the Table of Contents whose titles ' 'match the specified regular expression. Matching entries and all ' 'their children are removed.' ) ), OptionRecommendation(name='duplicate_links_in_toc', recommended_value=False, level=OptionRecommendation.LOW, help=_('When creating a TOC from links in the input document, ' 'allow duplicate entries, i.e. allow more than one entry ' 'with the same text, provided that they point to a ' 'different location.') ), OptionRecommendation(name='chapter', recommended_value="//*[((name()='h1' or name()='h2') and " r"re:test(., '\s*((chapter|book|section|part)\s+)|((prolog|prologue|epilogue)(\s+|$))', 'i')) or @class " "= 'chapter']", level=OptionRecommendation.LOW, help=_('An XPath expression to detect chapter titles. The default ' 'is to consider <h1> or <h2> tags that contain the words ' '"chapter","book","section", "prologue", "epilogue", or "part" as chapter titles as ' 'well as any tags that have class="chapter". The expression ' 'used must evaluate to a list of elements. To disable chapter ' 'detection, use the expression "/". See the XPath Tutorial ' 'in the calibre User Manual for further help on using this ' 'feature.' ) ), OptionRecommendation(name='chapter_mark', recommended_value='pagebreak', level=OptionRecommendation.LOW, choices=['pagebreak', 'rule', 'both', 'none'], help=_('Specify how to mark detected chapters. A value of ' '"pagebreak" will insert page breaks before chapters. ' 'A value of "rule" will insert a line before chapters. ' 'A value of "none" will disable chapter marking and a ' 'value of "both" will use both page breaks and lines ' 'to mark chapters.') ), OptionRecommendation(name='extra_css', recommended_value=None, level=OptionRecommendation.LOW, help=_('Either the path to a CSS stylesheet or raw CSS. ' 'This CSS will be appended to the style rules from ' 'the source file, so it can be used to override those ' 'rules.') ), OptionRecommendation(name='filter_css', recommended_value=None, level=OptionRecommendation.LOW, help=_('A comma separated list of CSS properties that ' 'will be removed from all CSS style rules. This is useful ' 'if the presence of some style information prevents it ' 'from being overridden on your device. ' 'For example: ' 'font-family,color,margin-left,margin-right') ), OptionRecommendation(name='page_breaks_before', recommended_value="//*[name()='h1' or name()='h2']", level=OptionRecommendation.LOW, help=_('An XPath expression. Page breaks are inserted ' 'before the specified elements.') ), OptionRecommendation(name='remove_fake_margins', recommended_value=True, level=OptionRecommendation.LOW, help=_('Some documents specify page margins by ' 'specifying a left and right margin on each individual ' 'paragraph. calibre will try to detect and remove these ' 'margins. Sometimes, this can cause the removal of ' 'margins that should not have been removed. In this ' 'case you can disable the removal.') ), OptionRecommendation(name='margin_top', recommended_value=5.0, level=OptionRecommendation.LOW, help=_('Set the top margin in pts. Default is %default. ' 'Setting this to less than zero will cause no margin to be set. ' 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='margin_bottom', recommended_value=5.0, level=OptionRecommendation.LOW, help=_('Set the bottom margin in pts. Default is %default. ' 'Setting this to less than zero will cause no margin to be set. ' 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='margin_left', recommended_value=5.0, level=OptionRecommendation.LOW, help=_('Set the left margin in pts. Default is %default. ' 'Setting this to less than zero will cause no margin to be set. ' 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='margin_right', recommended_value=5.0, level=OptionRecommendation.LOW, help=_('Set the right margin in pts. Default is %default. ' 'Setting this to less than zero will cause no margin to be set. ' 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='change_justification', recommended_value='original', level=OptionRecommendation.LOW, choices=['left','justify','original'], help=_('Change text justification. A value of "left" converts all' ' justified text in the source to left aligned (i.e. ' 'unjustified) text. A value of "justify" converts all ' 'unjustified text to justified. A value of "original" ' '(the default) does not change justification in the ' 'source file. Note that only some output formats support ' 'justification.')), OptionRecommendation(name='remove_paragraph_spacing', recommended_value=False, level=OptionRecommendation.LOW, help=_('Remove spacing between paragraphs. Also sets an indent on ' 'paragraphs of 1.5em. Spacing removal will not work ' 'if the source file does not use paragraphs (<p> or <div> tags).') ), OptionRecommendation(name='remove_paragraph_spacing_indent_size', recommended_value=1.5, level=OptionRecommendation.LOW, help=_('When calibre removes blank lines between paragraphs, it automatically ' 'sets a paragraph indent, to ensure that paragraphs can be easily ' 'distinguished. This option controls the width of that indent (in em). ' 'If you set this value negative, then the indent specified in the input ' 'document is used, that is, calibre does not change the indentation.') ), OptionRecommendation(name='prefer_metadata_cover', recommended_value=False, level=OptionRecommendation.LOW, help=_('Use the cover detected from the source file in preference ' 'to the specified cover.') ), OptionRecommendation(name='insert_blank_line', recommended_value=False, level=OptionRecommendation.LOW, help=_('Insert a blank line between paragraphs. Will not work ' 'if the source file does not use paragraphs (<p> or <div> tags).' ) ), OptionRecommendation(name='insert_blank_line_size', recommended_value=0.5, level=OptionRecommendation.LOW, help=_('Set the height of the inserted blank lines (in em).' ' The height of the lines between paragraphs will be twice the value' ' set here.') ), OptionRecommendation(name='remove_first_image', recommended_value=False, level=OptionRecommendation.LOW, help=_('Remove the first image from the input ebook. Useful if the ' 'input document has a cover image that is not identified as a cover. ' 'In this case, if you set a cover in calibre, the output document will ' 'end up with two cover images if you do not specify this option.' ) ), OptionRecommendation(name='insert_metadata', recommended_value=False, level=OptionRecommendation.LOW, help=_('Insert the book metadata at the start of ' 'the book. This is useful if your ebook reader does not support ' 'displaying/searching metadata directly.' ) ), OptionRecommendation(name='smarten_punctuation', recommended_value=False, level=OptionRecommendation.LOW, help=_('Convert plain quotes, dashes and ellipsis to their ' 'typographically correct equivalents. For details, see ' 'http://daringfireball.net/projects/smartypants' ) ), OptionRecommendation(name='unsmarten_punctuation', recommended_value=False, level=OptionRecommendation.LOW, help=_('Convert fancy quotes, dashes and ellipsis to their ' 'plain equivalents.' ) ), OptionRecommendation(name='read_metadata_from_opf', recommended_value=None, level=OptionRecommendation.LOW, short_switch='m', help=_('Read metadata from the specified OPF file. Metadata read ' 'from this file will override any metadata in the source ' 'file.') ), OptionRecommendation(name='asciiize', recommended_value=False, level=OptionRecommendation.LOW, help=(_('Transliterate unicode characters to an ASCII ' 'representation. Use with care because this will replace ' 'unicode characters with ASCII. For instance it will replace "%s" ' 'with "Mikhail Gorbachiov". Also, note that in ' 'cases where there are multiple representations of a character ' '(characters shared by Chinese and Japanese for instance) the ' 'representation based on the current calibre interface language will be ' 'used.')%\ u'\u041c\u0438\u0445\u0430\u0438\u043b ' u'\u0413\u043e\u0440\u0431\u0430\u0447\u0451\u0432' ) ), OptionRecommendation(name='keep_ligatures', recommended_value=False, level=OptionRecommendation.LOW, help=_('Preserve ligatures present in the input document. ' 'A ligature is a special rendering of a pair of ' 'characters like ff, fi, fl et cetera. ' 'Most readers do not have support for ' 'ligatures in their default fonts, so they are ' 'unlikely to render correctly. By default, calibre ' 'will turn a ligature into the corresponding pair of normal ' 'characters. This option will preserve them instead.') ), OptionRecommendation(name='title', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the title.')), OptionRecommendation(name='authors', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the authors. Multiple authors should be separated by ' 'ampersands.')), OptionRecommendation(name='title_sort', recommended_value=None, level=OptionRecommendation.LOW, help=_('The version of the title to be used for sorting. ')), OptionRecommendation(name='author_sort', recommended_value=None, level=OptionRecommendation.LOW, help=_('String to be used when sorting by author. ')), OptionRecommendation(name='cover', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the cover to the specified file or URL')), OptionRecommendation(name='comments', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the ebook description.')), OptionRecommendation(name='publisher', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the ebook publisher.')), OptionRecommendation(name='series', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the series this ebook belongs to.')), OptionRecommendation(name='series_index', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the index of the book in this series.')), OptionRecommendation(name='rating', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the rating. Should be a number between 1 and 5.')), OptionRecommendation(name='isbn', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the ISBN of the book.')), OptionRecommendation(name='tags', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the tags for the book. Should be a comma separated list.')), OptionRecommendation(name='book_producer', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the book producer.')), OptionRecommendation(name='language', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the language.')), OptionRecommendation(name='pubdate', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the publication date.')), OptionRecommendation(name='timestamp', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the book timestamp (no longer used anywhere)')), OptionRecommendation(name='enable_heuristics', recommended_value=False, level=OptionRecommendation.LOW, help=_('Enable heuristic processing. This option must be set for any ' 'heuristic processing to take place.')), OptionRecommendation(name='markup_chapter_headings', recommended_value=True, level=OptionRecommendation.LOW, help=_('Detect unformatted chapter headings and sub headings. Change ' 'them to h2 and h3 tags. This setting will not create a TOC, ' 'but can be used in conjunction with structure detection to create ' 'one.')), OptionRecommendation(name='italicize_common_cases', recommended_value=True, level=OptionRecommendation.LOW, help=_('Look for common words and patterns that denote ' 'italics and italicize them.')), OptionRecommendation(name='fix_indents', recommended_value=True, level=OptionRecommendation.LOW, help=_('Turn indentation created from multiple non-breaking space entities ' 'into CSS indents.')), OptionRecommendation(name='html_unwrap_factor', recommended_value=0.40, level=OptionRecommendation.LOW, help=_('Scale used to determine the length at which a line should ' 'be unwrapped. Valid values are a decimal between 0 and 1. The ' 'default is 0.4, just below the median line length. If only a ' 'few lines in the document require unwrapping this value should ' 'be reduced')), OptionRecommendation(name='unwrap_lines', recommended_value=True, level=OptionRecommendation.LOW, help=_('Unwrap lines using punctuation and other formatting clues.')), OptionRecommendation(name='delete_blank_paragraphs', recommended_value=True, level=OptionRecommendation.LOW, help=_('Remove empty paragraphs from the document when they exist between ' 'every other paragraph')), OptionRecommendation(name='format_scene_breaks', recommended_value=True, level=OptionRecommendation.LOW, help=_('Left aligned scene break markers are center aligned. ' 'Replace soft scene breaks that use multiple blank lines with ' 'horizontal rules.')), OptionRecommendation(name='replace_scene_breaks', recommended_value='', level=OptionRecommendation.LOW, help=_('Replace scene breaks with the specified text. By default, the ' 'text from the input document is used.')), OptionRecommendation(name='dehyphenate', recommended_value=True, level=OptionRecommendation.LOW, help=_('Analyze hyphenated words throughout the document. The ' 'document itself is used as a dictionary to determine whether hyphens ' 'should be retained or removed.')), OptionRecommendation(name='renumber_headings', recommended_value=True, level=OptionRecommendation.LOW, help=_('Looks for occurrences of sequential <h1> or <h2> tags. ' 'The tags are renumbered to prevent splitting in the middle ' 'of chapter headings.')), OptionRecommendation(name='sr1_search', recommended_value='', level=OptionRecommendation.LOW, help=_('Search pattern (regular expression) to be replaced with ' 'sr1-replace.')), OptionRecommendation(name='sr1_replace', recommended_value='', level=OptionRecommendation.LOW, help=_('Replacement to replace the text found with sr1-search.')), OptionRecommendation(name='sr2_search', recommended_value='', level=OptionRecommendation.LOW, help=_('Search pattern (regular expression) to be replaced with ' 'sr2-replace.')), OptionRecommendation(name='sr2_replace', recommended_value='', level=OptionRecommendation.LOW, help=_('Replacement to replace the text found with sr2-search.')), OptionRecommendation(name='sr3_search', recommended_value='', level=OptionRecommendation.LOW, help=_('Search pattern (regular expression) to be replaced with ' 'sr3-replace.')), OptionRecommendation(name='sr3_replace', recommended_value='', level=OptionRecommendation.LOW, help=_('Replacement to replace the text found with sr3-search.')), OptionRecommendation(name='search_replace', recommended_value=None, level=OptionRecommendation.LOW, help=_( 'Path to a file containing search and replace regular expressions. ' 'The file must contain alternating lines of regular expression ' 'followed by replacement pattern (which can be an empty line). ' 'The regular expression must be in the python regex syntax and ' 'the file must be UTF-8 encoded.')), ] # }}} input_fmt = os.path.splitext(self.input)[1] if not input_fmt: raise ValueError('Input file must have an extension') input_fmt = input_fmt[1:].lower().replace('original_', '') self.archive_input_tdir = None if input_fmt in ARCHIVE_FMTS: self.log('Processing archive...') tdir = PersistentTemporaryDirectory('_plumber_archive') self.input, input_fmt = self.unarchive(self.input, tdir) self.archive_input_tdir = tdir if os.access(self.input, os.R_OK): nfp = run_plugins_on_preprocess(self.input, input_fmt) if nfp != self.input: self.input = nfp input_fmt = os.path.splitext(self.input)[1] if not input_fmt: raise ValueError('Input file must have an extension') input_fmt = input_fmt[1:].lower() if os.path.exists(self.output) and os.path.isdir(self.output): output_fmt = 'oeb' else: output_fmt = os.path.splitext(self.output)[1] if not output_fmt: output_fmt = '.oeb' output_fmt = output_fmt[1:].lower() self.input_plugin = plugin_for_input_format(input_fmt) self.output_plugin = plugin_for_output_format(output_fmt) if self.input_plugin is None: raise ValueError('No plugin to handle input format: '+input_fmt) if self.output_plugin is None: raise ValueError('No plugin to handle output format: '+output_fmt) self.input_fmt = input_fmt self.output_fmt = output_fmt self.all_format_options = set() self.input_options = set() self.output_options = set() # Build set of all possible options. Two options are equal if their # names are the same. if not dummy: self.input_options = self.input_plugin.options.union( self.input_plugin.common_options) self.output_options = self.output_plugin.options.union( self.output_plugin.common_options) else: for fmt in available_input_formats(): input_plugin = plugin_for_input_format(fmt) if input_plugin: self.all_format_options = self.all_format_options.union( input_plugin.options.union(input_plugin.common_options)) for fmt in available_output_formats(): output_plugin = plugin_for_output_format(fmt) if output_plugin: self.all_format_options = self.all_format_options.union( output_plugin.options.union(output_plugin.common_options)) # Remove the options that have been disabled by recommendations from the # plugins. for w in ('input_options', 'output_options', 'all_format_options'): temp = set([]) for x in getattr(self, w): temp.add(x.clone()) setattr(self, w, temp) if merge_plugin_recs: self.merge_plugin_recommendations()
def supported_input_formats(): fmts = available_input_formats() for x in ('zip', 'rar', 'oebzip'): fmts.add(x) return fmts
def setup_completion(self): # {{{ try: self.info('Setting up command-line completion...') from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop from calibre.gui2.viewer.main import option_parser as viewer_op from calibre.gui2.tweak_book.main import option_parser as tweak_op from calibre.ebooks.metadata.sources.cli import option_parser as fem_op from calibre.gui2.main import option_parser as guiop from calibre.utils.smtp import option_parser as smtp_op from calibre.library.server.main import option_parser as serv_op from calibre.ebooks.oeb.polish.main import option_parser as polish_op, SUPPORTED from calibre.ebooks.oeb.polish.import_book import IMPORTABLE from calibre.debug import option_parser as debug_op from calibre.ebooks import BOOK_EXTENSIONS from calibre.customize.ui import available_input_formats input_formats = sorted(all_input_formats()) tweak_formats = sorted(x.lower() for x in SUPPORTED|IMPORTABLE) zsh = ZshCompleter(self.opts) bc = os.path.join(os.path.dirname(self.opts.staging_sharedir), 'bash-completion') if os.path.exists(bc): f = os.path.join(bc, 'calibre') else: if isnetbsd: f = os.path.join(self.opts.staging_root, 'share/bash_completion.d/calibre') else: f = os.path.join(self.opts.staging_etc, 'bash_completion.d/calibre') if not os.path.exists(os.path.dirname(f)): os.makedirs(os.path.dirname(f)) bash_comp_dest, zsh_comp_dest = f, None if zsh.dest: self.info('Installing zsh completion to:', zsh.dest) self.manifest.append(zsh.dest) zsh_comp_dest = zsh.dest complete = 'calibre-complete' if getattr(sys, 'frozen_path', None): complete = os.path.join(getattr(sys, 'frozen_path'), complete) self.info('Installing bash completion to', f) with open(f, 'wb') as f: def o_and_e(*args, **kwargs): f.write(opts_and_exts(*args, **kwargs)) zsh.opts_and_exts(*args, **kwargs) def o_and_w(*args, **kwargs): f.write(opts_and_words(*args, **kwargs)) zsh.opts_and_words(*args, **kwargs) f.write('# calibre Bash Shell Completion\n') o_and_e('calibre', guiop, BOOK_EXTENSIONS) o_and_e('lrf2lrs', lrf2lrsop, ['lrf'], file_map={'--output':['lrs']}) o_and_e('ebook-meta', metaop, list(meta_filetypes()), cover_opts=['--cover', '-c'], opf_opts=['--to-opf', '--from-opf']) o_and_e('ebook-polish', polish_op, [x.lower() for x in SUPPORTED], cover_opts=['--cover', '-c'], opf_opts=['--opf', '-o']) o_and_e('lrfviewer', lrfviewerop, ['lrf']) o_and_e('ebook-viewer', viewer_op, input_formats) o_and_e('ebook-edit', tweak_op, tweak_formats) o_and_w('fetch-ebook-metadata', fem_op, []) o_and_w('calibre-smtp', smtp_op, []) o_and_w('calibre-server', serv_op, []) o_and_e('calibre-debug', debug_op, ['py', 'recipe', 'mobi', 'azw', 'azw3', 'docx'], file_map={ '--tweak-book':['epub', 'azw3', 'mobi'], '--subset-font':['ttf', 'otf'], '--exec-file':['py', 'recipe'], '--add-simple-plugin':['py'], '--inspect-mobi':['mobi', 'azw', 'azw3'], '--viewer':list(available_input_formats()), }) f.write(textwrap.dedent(''' _ebook_device_ls() { local pattern search listing prefix pattern="$1" search="$1" if [[ -n "{$pattern}" ]]; then if [[ "${pattern:(-1)}" == "/" ]]; then pattern="" else pattern="$(basename ${pattern} 2> /dev/null)" search="$(dirname ${search} 2> /dev/null)" fi fi if [[ "x${search}" == "x" || "x${search}" == "x." ]]; then search="/" fi listing="$(ebook-device ls ${search} 2>/dev/null)" prefix="${search}" if [[ "x${prefix:(-1)}" != "x/" ]]; then prefix="${prefix}/" fi echo $(compgen -P "${prefix}" -W "${listing}" "${pattern}") } _ebook_device() { local cur prev cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() case "${prev}" in ls|rm|mkdir|touch|cat ) COMPREPLY=( $(_ebook_device_ls "${cur}") ) return 0 ;; cp ) if [[ ${cur} == dev:* ]]; then COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) return 0 else _filedir return 0 fi ;; dev ) COMPREPLY=( $(compgen -W "cp ls rm mkdir touch cat info books df" "${cur}") ) return 0 ;; * ) if [[ ${cur} == dev:* ]]; then COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) return 0 else if [[ ${prev} == dev:* ]]; then _filedir return 0 else COMPREPLY=( $(compgen -W "dev:" "${cur}") ) return 0 fi return 0 fi ;; esac } complete -o nospace -F _ebook_device ebook-device complete -o nospace -C %s ebook-convert ''')%complete) zsh.write() self.manifest.extend((bash_comp_dest, zsh_comp_dest)) except TypeError as err: if 'resolve_entities' in str(err): print 'You need python-lxml >= 2.0.5 for calibre' sys.exit(1) raise except EnvironmentError as e: if e.errno == errno.EACCES: self.warning('Failed to setup completion, permission denied') except: if self.opts.fatal_errors: raise self.task_failed('Setting up completion failed')
def is_supported(path): ext = os.path.splitext(path)[1].replace(".", "").lower() ext = re.sub(r"(x{0,1})htm(l{0,1})", "html", ext) return ext in available_input_formats()
def setup_completion(self): # {{{ try: self.info('Setting up command-line completion...') from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop from calibre.gui2.viewer.main import option_parser as viewer_op from calibre.gui2.tweak_book.main import option_parser as tweak_op from calibre.ebooks.metadata.sources.cli import option_parser as fem_op from calibre.gui2.main import option_parser as guiop from calibre.utils.smtp import option_parser as smtp_op from calibre.library.server.main import option_parser as serv_op from calibre.ebooks.oeb.polish.main import option_parser as polish_op, SUPPORTED from calibre.debug import option_parser as debug_op from calibre.ebooks import BOOK_EXTENSIONS from calibre.customize.ui import available_input_formats input_formats = sorted(all_input_formats()) tweak_formats = sorted(x.lower() for x in SUPPORTED) zsh = ZshCompleter(self.opts) bc = os.path.join(os.path.dirname(self.opts.staging_sharedir), 'bash-completion') if os.path.exists(bc): f = os.path.join(bc, 'calibre') else: if isnetbsd: f = os.path.join(self.opts.staging_root, 'share/bash_completion.d/calibre') else: f = os.path.join(self.opts.staging_etc, 'bash_completion.d/calibre') if not os.path.exists(os.path.dirname(f)): os.makedirs(os.path.dirname(f)) if zsh.dest: self.info('Installing zsh completion to:', zsh.dest) self.manifest.append(zsh.dest) self.manifest.append(f) complete = 'calibre-complete' if getattr(sys, 'frozen_path', None): complete = os.path.join(getattr(sys, 'frozen_path'), complete) self.info('Installing bash completion to', f) with open(f, 'wb') as f: def o_and_e(*args, **kwargs): f.write(opts_and_exts(*args, **kwargs)) zsh.opts_and_exts(*args, **kwargs) def o_and_w(*args, **kwargs): f.write(opts_and_words(*args, **kwargs)) zsh.opts_and_words(*args, **kwargs) f.write('# calibre Bash Shell Completion\n') o_and_e('calibre', guiop, BOOK_EXTENSIONS) o_and_e('lrf2lrs', lrf2lrsop, ['lrf'], file_map={'--output': ['lrs']}) o_and_e('ebook-meta', metaop, list(meta_filetypes()), cover_opts=['--cover', '-c'], opf_opts=['--to-opf', '--from-opf']) o_and_e('ebook-polish', polish_op, [x.lower() for x in SUPPORTED], cover_opts=['--cover', '-c'], opf_opts=['--opf', '-o']) o_and_e('lrfviewer', lrfviewerop, ['lrf']) o_and_e('ebook-viewer', viewer_op, input_formats) o_and_e('ebook-edit', tweak_op, tweak_formats) o_and_w('fetch-ebook-metadata', fem_op, []) o_and_w('calibre-smtp', smtp_op, []) o_and_w('calibre-server', serv_op, []) o_and_e('calibre-debug', debug_op, ['py', 'recipe', 'mobi', 'azw', 'azw3', 'docx'], file_map={ '--tweak-book': ['epub', 'azw3', 'mobi'], '--subset-font': ['ttf', 'otf'], '--exec-file': ['py', 'recipe'], '--add-simple-plugin': ['py'], '--inspect-mobi': ['mobi', 'azw', 'azw3'], '--viewer': list(available_input_formats()), }) f.write( textwrap.dedent(''' _ebook_device_ls() { local pattern search listing prefix pattern="$1" search="$1" if [[ -n "{$pattern}" ]]; then if [[ "${pattern:(-1)}" == "/" ]]; then pattern="" else pattern="$(basename ${pattern} 2> /dev/null)" search="$(dirname ${search} 2> /dev/null)" fi fi if [[ "x${search}" == "x" || "x${search}" == "x." ]]; then search="/" fi listing="$(ebook-device ls ${search} 2>/dev/null)" prefix="${search}" if [[ "x${prefix:(-1)}" != "x/" ]]; then prefix="${prefix}/" fi echo $(compgen -P "${prefix}" -W "${listing}" "${pattern}") } _ebook_device() { local cur prev cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() case "${prev}" in ls|rm|mkdir|touch|cat ) COMPREPLY=( $(_ebook_device_ls "${cur}") ) return 0 ;; cp ) if [[ ${cur} == dev:* ]]; then COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) return 0 else _filedir return 0 fi ;; dev ) COMPREPLY=( $(compgen -W "cp ls rm mkdir touch cat info books df" "${cur}") ) return 0 ;; * ) if [[ ${cur} == dev:* ]]; then COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) return 0 else if [[ ${prev} == dev:* ]]; then _filedir return 0 else COMPREPLY=( $(compgen -W "dev:" "${cur}") ) return 0 fi return 0 fi ;; esac } complete -o nospace -F _ebook_device ebook-device complete -o nospace -C %s ebook-convert ''') % complete) zsh.write() except TypeError as err: if 'resolve_entities' in str(err): print 'You need python-lxml >= 2.0.5 for calibre' sys.exit(1) raise except: if self.opts.fatal_errors: raise self.task_failed('Setting up completion failed')
def write_completion(bash_comp_dest, zsh): from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop from calibre.gui2.viewer.main import option_parser as viewer_op from calibre.gui2.tweak_book.main import option_parser as tweak_op from calibre.ebooks.metadata.sources.cli import option_parser as fem_op from calibre.gui2.main import option_parser as guiop from calibre.utils.smtp import option_parser as smtp_op from calibre.srv.standalone import create_option_parser as serv_op from calibre.ebooks.oeb.polish.main import option_parser as polish_op, SUPPORTED from calibre.ebooks.oeb.polish.import_book import IMPORTABLE from calibre.debug import option_parser as debug_op from calibre.ebooks import BOOK_EXTENSIONS from calibre.customize.ui import available_input_formats input_formats = sorted(all_input_formats()) tweak_formats = sorted(x.lower() for x in SUPPORTED | IMPORTABLE) if bash_comp_dest and not os.path.exists(os.path.dirname(bash_comp_dest)): os.makedirs(os.path.dirname(bash_comp_dest)) complete = 'calibre-complete' if getattr(sys, 'frozen_path', None): complete = os.path.join(getattr(sys, 'frozen_path'), complete) with open(bash_comp_dest or os.devnull, 'wb') as f: w = polyglot_write(f) def o_and_e(*args, **kwargs): w(opts_and_exts(*args, **kwargs)) zsh.opts_and_exts(*args, **kwargs) def o_and_w(*args, **kwargs): w(opts_and_words(*args, **kwargs)) zsh.opts_and_words(*args, **kwargs) w('# calibre Bash Shell Completion\n') o_and_e('calibre', guiop, BOOK_EXTENSIONS) o_and_e('lrf2lrs', lrf2lrsop, ['lrf'], file_map={'--output': ['lrs']}) o_and_e('ebook-meta', metaop, list(meta_filetypes()), cover_opts=['--cover', '-c'], opf_opts=['--to-opf', '--from-opf']) o_and_e('ebook-polish', polish_op, [x.lower() for x in SUPPORTED], cover_opts=['--cover', '-c'], opf_opts=['--opf', '-o']) o_and_e('lrfviewer', lrfviewerop, ['lrf']) o_and_e('ebook-viewer', viewer_op, input_formats) o_and_e('ebook-edit', tweak_op, tweak_formats) o_and_w('fetch-ebook-metadata', fem_op, []) o_and_w('calibre-smtp', smtp_op, []) o_and_w('calibre-server', serv_op, []) o_and_e('calibre-debug', debug_op, ['py', 'recipe', 'mobi', 'azw', 'azw3', 'docx'], file_map={ '--tweak-book': ['epub', 'azw3', 'mobi'], '--subset-font': ['ttf', 'otf'], '--exec-file': ['py', 'recipe'], '--add-simple-plugin': ['py'], '--inspect-mobi': ['mobi', 'azw', 'azw3'], '--viewer': sorted(available_input_formats()), }) w( textwrap.dedent(''' _ebook_device_ls() { local pattern search listing prefix pattern="$1" search="$1" if [[ -n "{$pattern}" ]]; then if [[ "${pattern:(-1)}" == "/" ]]; then pattern="" else pattern="$(basename ${pattern} 2> /dev/null)" search="$(dirname ${search} 2> /dev/null)" fi fi if [[ "x${search}" == "x" || "x${search}" == "x." ]]; then search="/" fi listing="$(ebook-device ls ${search} 2>/dev/null)" prefix="${search}" if [[ "x${prefix:(-1)}" != "x/" ]]; then prefix="${prefix}/" fi echo $(compgen -P "${prefix}" -W "${listing}" "${pattern}") } _ebook_device() { local cur prev cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() case "${prev}" in ls|rm|mkdir|touch|cat ) COMPREPLY=( $(_ebook_device_ls "${cur}") ) return 0 ;; cp ) if [[ ${cur} == dev:* ]]; then COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) return 0 else _filedir return 0 fi ;; dev ) COMPREPLY=( $(compgen -W "cp ls rm mkdir touch cat info books df" "${cur}") ) return 0 ;; * ) if [[ ${cur} == dev:* ]]; then COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) return 0 else if [[ ${prev} == dev:* ]]; then _filedir return 0 else COMPREPLY=( $(compgen -W "dev:" "${cur}") ) return 0 fi return 0 fi ;; esac } complete -o nospace -F _ebook_device ebook-device complete -o nospace -C %s ebook-convert ''') % complete) zsh.write()
def send_by_mail( self, to, fmts, delete_from_library, subject="", send_ids=None, do_auto_convert=True, specific_format=None ): ids = ( [self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()] if send_ids is None else send_ids ) if not ids or len(ids) == 0: return files, _auto_ids = self.library_view.model().get_preferred_formats_from_ids( ids, fmts, set_metadata=True, specific_format=specific_format, exclude_auto=do_auto_convert, use_plugboard=plugboard_email_value, plugboard_formats=plugboard_email_formats, ) if do_auto_convert: nids = list(set(ids).difference(_auto_ids)) ids = [i for i in ids if i in nids] else: _auto_ids = [] full_metadata = self.library_view.model().metadata_for(ids, get_cover=False) bad, remove_ids, jobnames = [], [], [] texts, subjects, attachments, attachment_names = [], [], [], [] for f, mi, id in zip(files, full_metadata, ids): t = mi.title if not t: t = _("Unknown") if f is None: bad.append(t) else: remove_ids.append(id) jobnames.append(t) attachments.append(f) if not subject: subjects.append(_("E-book:") + " " + t) else: components = get_components(subject, mi, id) if not components: components = [mi.title] subjects.append(os.path.join(*components)) a = authors_to_string(mi.authors if mi.authors else [_("Unknown")]) texts.append( _("Attached, you will find the e-book") + "\n\n" + t + "\n\t" + _("by") + " " + a + "\n\n" + _("in the %s format.") % os.path.splitext(f)[1][1:].upper() ) if mi.comments and gprefs["add_comments_to_email"]: from calibre.utils.html2text import html2text texts[-1] += "\n\n" + _("About this book:") + "\n\n" + textwrap.fill(html2text(mi.comments)) prefix = ascii_filename(t + " - " + a) if not isinstance(prefix, unicode): prefix = prefix.decode(preferred_encoding, "replace") attachment_names.append(prefix + os.path.splitext(f)[1]) remove = remove_ids if delete_from_library else [] to_s = list(repeat(to, len(attachments))) if attachments: send_mails( jobnames, Dispatcher(partial(self.email_sent, remove=remove)), attachments, to_s, subjects, texts, attachment_names, self.job_manager, ) self.status_bar.show_message(_("Sending email to") + " " + to, 3000) auto = [] if _auto_ids != []: for id in _auto_ids: if specific_format is None: dbfmts = self.library_view.model().db.formats(id, index_is_id=True) formats = [f.lower() for f in (dbfmts.split(",") if dbfmts else [])] if ( list(set(formats).intersection(available_input_formats())) != [] and list(set(fmts).intersection(available_output_formats())) != [] ): auto.append(id) else: bad.append(self.library_view.model().db.title(id, index_is_id=True)) else: if specific_format in list(set(fmts).intersection(set(available_output_formats()))): auto.append(id) else: bad.append(self.library_view.model().db.title(id, index_is_id=True)) if auto != []: format = ( specific_format if specific_format in list(set(fmts).intersection(set(available_output_formats()))) else None ) if not format: for fmt in fmts: if fmt in list(set(fmts).intersection(set(available_output_formats()))): format = fmt break if format is None: bad += auto else: autos = [self.library_view.model().db.title(id, index_is_id=True) for id in auto] if self.auto_convert_question( _("Auto convert the following books to %s before sending via " "email?") % format.upper(), autos ): self.iactions["Convert Books"].auto_convert_mail( to, fmts, delete_from_library, auto, format, subject ) if bad: bad = "\n".join("%s" % (i,) for i in bad) d = warning_dialog( self, _("No suitable formats"), _("Could not email the following books " "as no suitable formats were found:"), bad, ) d.exec_()
def send_by_mail(self, to, fmts, delete_from_library, subject='', send_ids=None, do_auto_convert=True, specific_format=None): ids = [self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()] if send_ids is None else send_ids if not ids or len(ids) == 0: return files, _auto_ids = self.library_view.model().get_preferred_formats_from_ids(ids, fmts, set_metadata=True, specific_format=specific_format, exclude_auto=do_auto_convert, use_plugboard=plugboard_email_value, plugboard_formats=plugboard_email_formats) if do_auto_convert: nids = list(set(ids).difference(_auto_ids)) ids = [i for i in ids if i in nids] else: _auto_ids = [] full_metadata = self.library_view.model().metadata_for(ids, get_cover=False) bad, remove_ids, jobnames = [], [], [] texts, subjects, attachments, attachment_names = [], [], [], [] for f, mi, id in zip(files, full_metadata, ids): t = mi.title if not t: t = _('Unknown') if f is None: bad.append(t) else: remove_ids.append(id) jobnames.append(t) attachments.append(f) if not subject: subjects.append(_('E-book:')+ ' '+t) else: components = get_components(subject, mi, id) if not components: components = [mi.title] subjects.append(os.path.join(*components)) a = authors_to_string(mi.authors if mi.authors else [_('Unknown')]) texts.append(_('Attached, you will find the e-book') + '\n\n' + t + '\n\t' + _('by') + ' ' + a + '\n\n' + _('in the %s format.') % os.path.splitext(f)[1][1:].upper()) prefix = ascii_filename(t+' - '+a) if not isinstance(prefix, unicode): prefix = prefix.decode(preferred_encoding, 'replace') attachment_names.append(prefix + os.path.splitext(f)[1]) remove = remove_ids if delete_from_library else [] to_s = list(repeat(to, len(attachments))) if attachments: send_mails(jobnames, Dispatcher(partial(self.email_sent, remove=remove)), attachments, to_s, subjects, texts, attachment_names, self.job_manager) self.status_bar.show_message(_('Sending email to')+' '+to, 3000) auto = [] if _auto_ids != []: for id in _auto_ids: if specific_format is None: dbfmts = self.library_view.model().db.formats(id, index_is_id=True) formats = [f.lower() for f in (dbfmts.split(',') if dbfmts else [])] if list(set(formats).intersection(available_input_formats())) != [] and list(set(fmts).intersection(available_output_formats())) != []: auto.append(id) else: bad.append(self.library_view.model().db.title(id, index_is_id=True)) else: if specific_format in list(set(fmts).intersection(set(available_output_formats()))): auto.append(id) else: bad.append(self.library_view.model().db.title(id, index_is_id=True)) if auto != []: format = specific_format if specific_format in list(set(fmts).intersection(set(available_output_formats()))) else None if not format: for fmt in fmts: if fmt in list(set(fmts).intersection(set(available_output_formats()))): format = fmt break if format is None: bad += auto else: autos = [self.library_view.model().db.title(id, index_is_id=True) for id in auto] if self.auto_convert_question( _('Auto convert the following books to %s before sending via ' 'email?') % format.upper(), autos): self.iactions['Convert Books'].auto_convert_mail(to, fmts, delete_from_library, auto, format, subject) if bad: bad = '\n'.join('%s'%(i,) for i in bad) d = warning_dialog(self, _('No suitable formats'), _('Could not email the following books ' 'as no suitable formats were found:'), bad) d.exec_()
def write_completion(bash_comp_dest, zsh): from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop from calibre.gui2.viewer.main import option_parser as viewer_op from calibre.gui2.tweak_book.main import option_parser as tweak_op from calibre.ebooks.metadata.sources.cli import option_parser as fem_op from calibre.gui2.main import option_parser as guiop from calibre.utils.smtp import option_parser as smtp_op from calibre.library.server.main import option_parser as serv_op from calibre.ebooks.oeb.polish.main import option_parser as polish_op, SUPPORTED from calibre.ebooks.oeb.polish.import_book import IMPORTABLE from calibre.debug import option_parser as debug_op from calibre.ebooks import BOOK_EXTENSIONS from calibre.customize.ui import available_input_formats input_formats = sorted(all_input_formats()) tweak_formats = sorted(x.lower() for x in SUPPORTED | IMPORTABLE) if bash_comp_dest and not os.path.exists(os.path.dirname(bash_comp_dest)): os.makedirs(os.path.dirname(bash_comp_dest)) complete = "calibre-complete" if getattr(sys, "frozen_path", None): complete = os.path.join(getattr(sys, "frozen_path"), complete) with open(bash_comp_dest or os.devnull, "wb") as f: def o_and_e(*args, **kwargs): f.write(opts_and_exts(*args, **kwargs)) zsh.opts_and_exts(*args, **kwargs) def o_and_w(*args, **kwargs): f.write(opts_and_words(*args, **kwargs)) zsh.opts_and_words(*args, **kwargs) f.write("# calibre Bash Shell Completion\n") o_and_e("calibre", guiop, BOOK_EXTENSIONS) o_and_e("lrf2lrs", lrf2lrsop, ["lrf"], file_map={"--output": ["lrs"]}) o_and_e( "ebook-meta", metaop, list(meta_filetypes()), cover_opts=["--cover", "-c"], opf_opts=["--to-opf", "--from-opf"], ) o_and_e( "ebook-polish", polish_op, [x.lower() for x in SUPPORTED], cover_opts=["--cover", "-c"], opf_opts=["--opf", "-o"], ) o_and_e("lrfviewer", lrfviewerop, ["lrf"]) o_and_e("ebook-viewer", viewer_op, input_formats) o_and_e("ebook-edit", tweak_op, tweak_formats) o_and_w("fetch-ebook-metadata", fem_op, []) o_and_w("calibre-smtp", smtp_op, []) o_and_w("calibre-server", serv_op, []) o_and_e( "calibre-debug", debug_op, ["py", "recipe", "mobi", "azw", "azw3", "docx"], file_map={ "--tweak-book": ["epub", "azw3", "mobi"], "--subset-font": ["ttf", "otf"], "--exec-file": ["py", "recipe"], "--add-simple-plugin": ["py"], "--inspect-mobi": ["mobi", "azw", "azw3"], "--viewer": list(available_input_formats()), }, ) f.write( textwrap.dedent( """ _ebook_device_ls() { local pattern search listing prefix pattern="$1" search="$1" if [[ -n "{$pattern}" ]]; then if [[ "${pattern:(-1)}" == "/" ]]; then pattern="" else pattern="$(basename ${pattern} 2> /dev/null)" search="$(dirname ${search} 2> /dev/null)" fi fi if [[ "x${search}" == "x" || "x${search}" == "x." ]]; then search="/" fi listing="$(ebook-device ls ${search} 2>/dev/null)" prefix="${search}" if [[ "x${prefix:(-1)}" != "x/" ]]; then prefix="${prefix}/" fi echo $(compgen -P "${prefix}" -W "${listing}" "${pattern}") } _ebook_device() { local cur prev cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() case "${prev}" in ls|rm|mkdir|touch|cat ) COMPREPLY=( $(_ebook_device_ls "${cur}") ) return 0 ;; cp ) if [[ ${cur} == dev:* ]]; then COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) return 0 else _filedir return 0 fi ;; dev ) COMPREPLY=( $(compgen -W "cp ls rm mkdir touch cat info books df" "${cur}") ) return 0 ;; * ) if [[ ${cur} == dev:* ]]; then COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) return 0 else if [[ ${prev} == dev:* ]]; then _filedir return 0 else COMPREPLY=( $(compgen -W "dev:" "${cur}") ) return 0 fi return 0 fi ;; esac } complete -o nospace -F _ebook_device ebook-device complete -o nospace -C %s ebook-convert """ ) % complete ) zsh.write()
def is_supported(path): ext = os.path.splitext(path)[1].replace('.', '').lower() ext = re.sub(r'(x{0,1})htm(l{0,1})', 'html', ext) return ext in available_input_formats() or ext == 'kepub'
def interface_data(ctx, rd): ''' Return the data needed to create the server main UI Optional: ?num=50&sort=timestamp.desc&library_id=<default library> &search=''&extra_books='' ''' ans = { 'username': rd.username, 'output_format': prefs['output_format'].upper(), 'input_formats': {x.upper(): True for x in available_input_formats()}, 'gui_pubdate_display_format': tweaks['gui_pubdate_display_format'], 'gui_timestamp_display_format': tweaks['gui_timestamp_display_format'], 'gui_last_modified_display_format': tweaks['gui_last_modified_display_format'], 'use_roman_numerals_for_series_number': get_use_roman(), 'translations': get_translations(), } ans['library_map'], ans['default_library'] = ctx.library_info(rd) ud = {} if rd.username: # Override session data with stored values for the authenticated user, # if any ud = ctx.user_manager.get_session_data(rd.username) lid = ud.get('library_id') if lid and lid in ans['library_map']: rd.query.set('library_id', lid) usort = ud.get('sort') if usort: rd.query.set('sort', usort) ans['library_id'], db, sorts, orders = get_basic_query_data(ctx, rd) ans['user_session_data'] = ud try: num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS)) except Exception: raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num')) with db.safe_read_lock: try: ans['search_result'] = search_result(ctx, rd, db, rd.query.get('search', ''), num, 0, ','.join(sorts), ','.join(orders)) except ParseException: ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders)) sf = db.field_metadata.ui_sortable_field_keys() sf.pop('ondevice', None) ans['sortable_fields'] = sorted( ((sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()), key=lambda (field, name): sort_key(name)) ans['field_metadata'] = db.field_metadata.all_metadata() ans['icon_map'] = icon_map() ans['icon_path'] = ctx.url_for('/icon', which='') mdata = ans['metadata'] = {} try: extra_books = set( int(x) for x in rd.query.get('extra_books', '').split(',')) except Exception: extra_books = () for coll in (ans['search_result']['book_ids'], extra_books): for book_id in coll: if book_id not in mdata: data = book_as_json(db, book_id) if data is not None: mdata[book_id] = data return ans
def send_by_mail(self, to, fmts, delete_from_library, subject='', send_ids=None, do_auto_convert=True, specific_format=None): ids = [ self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows() ] if send_ids is None else send_ids if not ids or len(ids) == 0: return files, _auto_ids = self.library_view.model( ).get_preferred_formats_from_ids( ids, fmts, set_metadata=True, specific_format=specific_format, exclude_auto=do_auto_convert, use_plugboard=plugboard_email_value, plugboard_formats=plugboard_email_formats) if do_auto_convert: nids = list(set(ids).difference(_auto_ids)) ids = [i for i in ids if i in nids] else: _auto_ids = [] full_metadata = self.library_view.model().metadata_for(ids, get_cover=False) bad, remove_ids, jobnames = [], [], [] texts, subjects, attachments, attachment_names = [], [], [], [] for f, mi, id in zip(files, full_metadata, ids): t = mi.title if not t: t = _('Unknown') if f is None: bad.append(t) else: remove_ids.append(id) jobnames.append(t) attachments.append(f) if not subject: subjects.append(_('E-book:') + ' ' + t) else: components = get_components(subject, mi, id) if not components: components = [mi.title] subjects.append(os.path.join(*components)) a = authors_to_string(mi.authors if mi.authors else \ [_('Unknown')]) texts.append(_('Attached, you will find the e-book') + \ '\n\n' + t + '\n\t' + _('by') + ' ' + a + '\n\n' + \ _('in the %s format.') % os.path.splitext(f)[1][1:].upper()) prefix = ascii_filename(t + ' - ' + a) if not isinstance(prefix, unicode): prefix = prefix.decode(preferred_encoding, 'replace') attachment_names.append(prefix + os.path.splitext(f)[1]) remove = remove_ids if delete_from_library else [] to_s = list(repeat(to, len(attachments))) if attachments: send_mails(jobnames, Dispatcher(partial(self.email_sent, remove=remove)), attachments, to_s, subjects, texts, attachment_names, self.job_manager) self.status_bar.show_message( _('Sending email to') + ' ' + to, 3000) auto = [] if _auto_ids != []: for id in _auto_ids: if specific_format == None: dbfmts = self.library_view.model().db.formats( id, index_is_id=True) formats = [ f.lower() for f in (dbfmts.split(',') if dbfmts else []) ] if list( set(formats).intersection( available_input_formats())) != [] and list( set(fmts).intersection( available_output_formats())) != []: auto.append(id) else: bad.append(self.library_view.model().db.title( id, index_is_id=True)) else: if specific_format in list( set(fmts).intersection( set(available_output_formats()))): auto.append(id) else: bad.append(self.library_view.model().db.title( id, index_is_id=True)) if auto != []: format = specific_format if specific_format in list( set(fmts).intersection(set( available_output_formats()))) else None if not format: for fmt in fmts: if fmt in list( set(fmts).intersection( set(available_output_formats()))): format = fmt break if format is None: bad += auto else: autos = [ self.library_view.model().db.title(id, index_is_id=True) for id in auto ] if self.auto_convert_question( _('Auto convert the following books before sending via ' 'email?'), autos): self.iactions['Convert Books'].auto_convert_mail( to, fmts, delete_from_library, auto, format, subject) if bad: bad = '\n'.join('%s' % (i, ) for i in bad) d = warning_dialog( self, _('No suitable formats'), _('Could not email the following books ' 'as no suitable formats were found:'), bad) d.exec_()
def interface_data(ctx, rd): """ Return the data needed to create the server main UI Optional: ?num=50&sort=timestamp.desc&library_id=<default library> &search=''&extra_books='' """ ans = { "username": rd.username, "output_format": prefs["output_format"].upper(), "input_formats": {x.upper(): True for x in available_input_formats()}, "gui_pubdate_display_format": tweaks["gui_pubdate_display_format"], "gui_timestamp_display_format": tweaks["gui_timestamp_display_format"], "gui_last_modified_display_format": tweaks["gui_last_modified_display_format"], "use_roman_numerals_for_series_number": get_use_roman(), } ans["library_map"], ans["default_library"] = ctx.library_map ud = {} if rd.username: # Override session data with stored values for the authenticated user, # if any ud = ctx.user_manager.get_session_data(rd.username) lid = ud.get("library_id") if lid and lid in ans["library_map"]: rd.query.set("library_id", lid) usort = ud.get("sort") if usort: rd.query.set("sort", usort) ans["library_id"], db, sorts, orders = get_basic_query_data(ctx, rd.query) ans["user_session_data"] = ud try: num = int(rd.query.get("num", DEFAULT_NUMBER_OF_BOOKS)) except Exception: raise HTTPNotFound("Invalid number of books: %r" % rd.query.get("num")) with db.safe_read_lock: try: ans["search_result"] = search_result( ctx, rd, db, rd.query.get("search", ""), num, 0, ",".join(sorts), ",".join(orders) ) except ParseException: ans["search_result"] = search_result(ctx, rd, db, "", num, 0, ",".join(sorts), ",".join(orders)) sf = db.field_metadata.ui_sortable_field_keys() sf.pop("ondevice", None) ans["sortable_fields"] = sorted( ((sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()), key=lambda (field, name): sort_key(name), ) ans["field_metadata"] = db.field_metadata.all_metadata() ans["icon_map"] = icon_map() ans["icon_path"] = ctx.url_for("/icon", which="") mdata = ans["metadata"] = {} try: extra_books = set(int(x) for x in rd.query.get("extra_books", "").split(",")) except Exception: extra_books = () for coll in (ans["search_result"]["book_ids"], extra_books): for book_id in coll: if book_id not in mdata: data = book_as_json(db, book_id) if data is not None: mdata[book_id] = data return ans