diff --git a/src/calibre/ebooks/conversion/config.py b/src/calibre/ebooks/conversion/config.py index 872171302c..4af7ccaeb1 100644 --- a/src/calibre/ebooks/conversion/config.py +++ b/src/calibre/ebooks/conversion/config.py @@ -45,6 +45,20 @@ def load_defaults(name): return r +def load_all_defaults(): + ans = {} + for x in os.listdir(config_dir): + if x.endswith('.py'): + path = os.path.join(config_dir, x) + with ExclusiveFile(path) as f: + raw = f.read() + r = GuiRecommendations() + if raw: + r.deserialize(raw) + ans[os.path.splitext(x)[0]] = r.copy() + return ans + + def save_specifics(db, book_id, recs): raw = recs.serialize() db.set_conversion_options(book_id, 'PIPE', raw) @@ -146,14 +160,11 @@ def get_preferred_input_format_for_book(db, book_id): def sort_formats_by_preference(formats, prefs): - uprefs = [x.upper() for x in prefs] + uprefs = {x.upper():i for i, x in enumerate(prefs)} def key(x): - try: - return uprefs.index(x.upper()) - except ValueError: - pass - return len(prefs) + return uprefs.get(x.upper(), len(prefs)) + return sorted(formats, key=key) @@ -188,5 +199,16 @@ def get_output_formats(preferred_output_format): fmts.append(pfo) else: fmts = list(sorted(all_formats, - key=lambda x:{'EPUB':'!A', 'MOBI':'!B'}.get(x.upper(), x))) + key=lambda x:{'EPUB':'!A', 'AZW3':'!B', 'MOBI':'!C'}.get(x.upper(), x))) + return fmts + + +def get_sorted_output_formats(): + preferred_output_format = prefs['output_format'].upper() + fmts = get_output_formats(preferred_output_format) + try: + fmts.remove(preferred_output_format) + except Exception: + pass + fmts.insert(0, preferred_output_format) return fmts diff --git a/src/calibre/srv/convert.py b/src/calibre/srv/convert.py new file mode 100644 index 0000000000..74ab8ea317 --- /dev/null +++ b/src/calibre/srv/convert.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python2 +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2018, Kovid Goyal + +from __future__ import absolute_import, division, print_function, unicode_literals + +from calibre.srv.errors import BookNotFound +from calibre.srv.routes import endpoint, json +from calibre.srv.utils import get_library_data + + +def conversion_defaults(): + from calibre.ebooks.conversion.config import load_all_defaults + ans = getattr(conversion_defaults, 'ans', None) + if ans is None: + ans = conversion_defaults.ans = load_all_defaults() + return ans + + +@endpoint('/conversion/book-data/{book_id}', postprocess=json, types={'book_id': int}) +def conversion_data(ctx, rd, book_id): + from calibre.ebooks.conversion.config import ( + NoSupportedInputFormats, get_input_format_for_book, + get_sorted_output_formats, load_specifics) + db = get_library_data(ctx, rd)[0] + if not ctx.has_id(rd, db, book_id): + raise BookNotFound(book_id, db) + try: + input_format, input_formats = get_input_format_for_book(db, book_id) + except NoSupportedInputFormats: + input_formats = [] + else: + if input_format in input_formats: + input_formats.remove(input_format) + input_formats.insert(0, input_format) + ans = { + 'input_formats': [x.upper() for x in input_formats], + 'output_formats': get_sorted_output_formats(), + 'conversion_defaults': conversion_defaults(), + 'conversion_specifics': load_specifics(db, book_id), + 'title': db.field_for('title', book_id), + 'authors': db.field_for('authors', book_id), + } + return ans diff --git a/src/calibre/srv/handler.py b/src/calibre/srv/handler.py index 421d313342..95d0b67111 100644 --- a/src/calibre/srv/handler.py +++ b/src/calibre/srv/handler.py @@ -179,7 +179,7 @@ class Context(object): return old[1] -SRV_MODULES = ('ajax', 'books', 'cdb', 'code', 'content', 'legacy', 'opds', 'users_api') +SRV_MODULES = ('ajax', 'books', 'cdb', 'code', 'content', 'legacy', 'opds', 'users_api', 'convert') class Handler(object): diff --git a/src/pyj/book_list/book_details.pyj b/src/pyj/book_list/book_details.pyj index 7efc129943..0eabcb962b 100644 --- a/src/pyj/book_list/book_details.pyj +++ b/src/pyj/book_list/book_details.pyj @@ -14,7 +14,7 @@ from book_list.library_data import ( book_metadata, cover_url, current_library_id, current_virtual_library, download_url, library_data, load_status, set_book_metadata ) -from book_list.router import back, home, open_book +from book_list.router import back, home, open_book, report_a_load_failure from book_list.theme import get_color, get_font_size from book_list.top_bar import add_button, clear_buttons, create_top_bar, set_title from book_list.ui import query_as_href, set_panel_handler, show_panel @@ -546,17 +546,10 @@ def create_book_details(container): def report_load_failure(container): - err = E.div() - safe_set_inner_html(err, load_status.error_html) - container.appendChild(E.div( - style='margin: 1ex 1em', - E.div(_('Failed to load books from calibre library, with error:')), - err, - E.div( - style='margin-top: 1em; border-top: solid 1px currentColor; padding-top: 1ex;', - E.a(onclick=def(): home(replace=True);, href='javascript: void(0)', style='color: blue', _('Go back to the home page'))) - ), - ) + report_a_load_failure( + container, _('Failed to load books from calibre library, with error:'), + load_status.error_html) + def check_for_books_loaded(): diff --git a/src/pyj/book_list/convert_book.pyj b/src/pyj/book_list/convert_book.pyj new file mode 100644 index 0000000000..789b4d1aed --- /dev/null +++ b/src/pyj/book_list/convert_book.pyj @@ -0,0 +1,89 @@ +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2018, Kovid Goyal +from __python__ import bound_methods, hash_literals + +from elementmaker import E +from gettext import gettext as _ + +from ajax import ajax +from book_list.book_details import report_load_failure +from book_list.library_data import load_status, url_books_query +from book_list.router import back, report_a_load_failure +from book_list.top_bar import create_top_bar +from book_list.ui import set_panel_handler +from dom import add_extra_css, build_rule, clear +from utils import conditional_timeout, parse_url_params + +CLASS_NAME = 'convert-book-panel' + +add_extra_css(def(): + sel = '.' + CLASS_NAME + ' ' + style = build_rule(sel, placeholder='TODO: add this') + return style +) + + +conversion_data = None +conversion_data_load_status = {'loading':True, 'ok':False, 'error_html':None, 'current_fetch': None} + + +def on_data_loaded(end_type, xhr, ev): + nonlocal conversion_data + conversion_data_load_status.current_fetch = None + + def bad_load(msg): + conversion_data_load_status.ok = False + conversion_data_load_status.loading = False + conversion_data_load_status.error_html = msg or xhr.error_html + + if end_type is 'load': + conversion_data = JSON.parse(xhr.responseText) + elif end_type is 'abort': + pass + else: + bad_load() + + +def fetch_conversion_data(book_id): + if conversion_data_load_status.current_fetch: + conversion_data_load_status.current_fetch.abort() + query = url_books_query() + conversion_data_load_status.loading = True + conversion_data_load_status.ok = False + conversion_data_load_status.error_html = None + conversion_data_load_status.current_fetch = ajax(f'conversion/books-data/{book_id}', on_data_loaded, query=query) + conversion_data_load_status.current_fetch.send() + + +def on_close(container_id): + back() + + +def check_for_data_loaded(): + container = this + if load_status.loading or conversion_data_load_status.loading: + conditional_timeout(container.id, 5, check_for_data_loaded) + return + container = container.lastChild + clear(container) + if not load_status.ok: + report_load_failure(container) + return + if not conversion_data_load_status.ok: + report_a_load_failure( + container, _('Failed to load conversion data from calibre, with error:'), + conversion_data_load_status.error_html) + # create_convert_book(container) + + +def init(container_id): + container = document.getElementById(container_id) + create_top_bar(container, title=_('Convert book'), action=on_close.bind(None, container_id), icon='close') + container.appendChild(E.div(class_=CLASS_NAME)) + container.lastChild.appendChild(E.div(_('Loading conversion data, please wait...'), style='margin: 1ex 1em')) + conditional_timeout(container_id, 5, check_for_data_loaded) + q = parse_url_params() + fetch_conversion_data(q.book_id) + + +set_panel_handler('convert_book', init) diff --git a/src/pyj/book_list/router.pyj b/src/pyj/book_list/router.pyj index b4c50b84f6..bf9ea6b64f 100644 --- a/src/pyj/book_list/router.pyj +++ b/src/pyj/book_list/router.pyj @@ -2,11 +2,17 @@ # License: GPL v3 Copyright: 2017, Kovid Goyal from __python__ import bound_methods, hash_literals +from elementmaker import E +from gettext import gettext as _ + from book_list.constants import book_list_container_id, read_book_container_id from book_list.globals import get_current_query from book_list.library_data import current_library_id from modals import close_all_modals -from utils import encode_query_with_path, parse_url_params, request_full_screen +from utils import ( + encode_query_with_path, parse_url_params, request_full_screen, + safe_set_inner_html +) mode_handlers = {} default_mode_handler = None @@ -89,5 +95,20 @@ def back(): q = {} push_state(q, replace=True) + def home(replace=False): push_state({}) + + +def report_a_load_failure(container, msg, error_html): + err = E.div() + safe_set_inner_html(err, error_html) + container.appendChild(E.div( + style='margin: 1ex 1em', + E.div(msg), + err, + E.div( + style='margin-top: 1em; border-top: solid 1px currentColor; padding-top: 1ex;', + E.a(onclick=def(): home(replace=True);, href='javascript: void(0)', style='color: blue', _('Go back to the home page'))) + ), + )