From 6be32931661d0280d9149562f3318157748f36b2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 19 Feb 2024 10:33:10 +0530 Subject: [PATCH] More work on reader profiles --- src/calibre/gui2/viewer/config.py | 50 +++++++++++++++++++++++++++++ src/calibre/gui2/viewer/web_view.py | 12 ++++++- src/pyj/session.pyj | 25 +++++++++++---- src/pyj/viewer-main.pyj | 29 +++++++++++++++++ 4 files changed, 108 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/viewer/config.py b/src/calibre/gui2/viewer/config.py index 2efd96e053..ef62fbd72e 100644 --- a/src/calibre/gui2/viewer/config.py +++ b/src/calibre/gui2/viewer/config.py @@ -7,6 +7,7 @@ import tempfile from calibre.constants import cache_dir, config_dir from calibre.utils.config import JSONConfig +from calibre.utils.date import isoformat, utcnow from calibre.utils.filenames import atomic_rename vprefs = JSONConfig('viewer-webengine') @@ -73,3 +74,52 @@ def save_reading_rates(key, rates): def load_reading_rates(key): existing = get_existing_reading_rates() return existing.get(key) + + +def expand_profile_user_names(user_names): + user_names = set(user_names) + sau = get_session_pref('sync_annots_user', default='') + if sau: + if sau == '*': + sau = 'user:' + if 'viewer:' in user_names: + user_names.add(sau) + elif sau in user_names: + user_names.add('viewer:') + return user_names + + +def load_viewer_profiles(*user_names: str, as_json_string=False): + user_names = expand_profile_user_names(user_names) + ans = {} + try: + with open(os.path.join(viewer_config_dir, 'profiles.json'), 'rb') as f: + raw = json.loads(f.read()) + except FileNotFoundError: + if as_json_string: + return '{}' + return ans + for uname, profiles in raw.items(): + if uname in user_names: + for profile_name, profile in profiles.items(): + if profile_name not in ans or ans[profile_name]['__timestamp__'] <= profile['__timestamp__']: + ans[profile_name] = profile + if as_json_string: + return json.dumps(ans) + return ans + + +def save_viewer_profile(profile_name, profile, *user_names: str): + user_names = expand_profile_user_names(user_names) + if isinstance(profile, (str, bytes)): + profile = json.loads(profile) + profile['__timestamp__'] = isoformat(utcnow()) + try: + with open(os.path.join(viewer_config_dir, 'profiles.json'), 'rb') as f: + raw = json.loads(f.read()) + except FileNotFoundError: + raw = {} + for name in user_names: + raw.setdefault(name, {})[profile_name] = profile + with open(os.path.join(viewer_config_dir, 'profiles.json'), 'wb') as f: + f.write(json.dumps(raw, indent=2, sort_keys=True).encode()) diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index 38e8a71295..c563f89696 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -25,7 +25,9 @@ from calibre.gui2 import choose_images, config, error_dialog, safe_open_url from calibre.gui2.viewer import ( link_prefix_for_location_links, performance_monitor, url_for_book_in_library, ) -from calibre.gui2.viewer.config import viewer_config_dir, vprefs +from calibre.gui2.viewer.config import ( + load_viewer_profiles, save_viewer_profile, viewer_config_dir, vprefs, +) from calibre.gui2.viewer.tts import TTS from calibre.gui2.webengine import RestartingWebEngineView from calibre.srv.code import get_translations_data @@ -188,6 +190,9 @@ class UrlSchemeHandler(QWebEngineUrlSchemeHandler): encoded_fname = name[len('reader-background-'):] mt, data = background_image(encoded_fname) send_reply(rq, mt, data) if data else rq.fail(QWebEngineUrlRequestJob.Error.UrlNotFound) + elif name == 'all-profiles': + vp = load_viewer_profiles('viewer:', as_json_string=True) + send_reply(rq, 'application/json', vp.encode()) elif name.startswith('mathjax/'): handle_mathjax_request(rq, name) elif not name: @@ -282,6 +287,7 @@ class ViewerBridge(Bridge): show_book_folder = from_js() show_help = from_js(object) update_reading_rates = from_js(object) + save_profile = from_js(object, object) create_view = to_js() start_book_load = to_js() @@ -546,6 +552,7 @@ class WebView(RestartingWebEngineView): self.bridge.close_prep_finished.connect(self.close_prep_finished) self.bridge.highlights_changed.connect(self.highlights_changed) self.bridge.update_reading_rates.connect(self.update_reading_rates) + self.bridge.save_profile.connect(self.save_profile) self.bridge.edit_book.connect(self.edit_book) self.bridge.show_book_folder.connect(self.show_book_folder) self.bridge.show_help.connect(self.show_help) @@ -565,6 +572,9 @@ class WebView(RestartingWebEngineView): self.inspector = Inspector(parent.inspector_dock.toggleViewAction(), self) parent.inspector_dock.setWidget(self.inspector) + def save_profile(self, name, settings): + save_viewer_profile(name, settings, 'viewer:') + def link_hovered(self, url): if url == 'javascript:void(0)': url = '' diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj index a30bb4fdb2..5362b38258 100644 --- a/src/pyj/session.pyj +++ b/src/pyj/session.pyj @@ -29,8 +29,8 @@ all_settings = { 'base_font_size': {'default': 16, 'category': 'read_book', 'is_local': True}, 'book_scrollbar': {'default': False, 'category': 'read_book'}, 'columns_per_screen': {'default': {'portrait':0, 'landscape':0}, 'category': 'read_book', 'is_local': True}, - 'controls_help_shown_count': {'default': 0, 'category': 'read_book', 'is_local': True}, - 'controls_help_shown_count_rtl_page_progression': {'default': 0, 'category': 'read_book', 'is_local': True}, + 'controls_help_shown_count': {'default': 0, 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True}, + 'controls_help_shown_count_rtl_page_progression': {'default': 0, 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True}, 'cover_preserve_aspect_ratio': {'default': True, 'category': 'read_book'}, 'current_color_scheme': {'default': 'system', 'category': 'read_book'}, 'footer': {'default': {'right': 'progress'}, 'category': 'read_book'}, @@ -58,7 +58,7 @@ all_settings = { 'scroll_stop_boundaries': {'default': False, 'category': 'read_book', 'is_local': True}, 'standalone_font_settings': {'default': {}, 'category': 'read_book', 'is_local': True}, 'standalone_misc_settings': {'default': {}, 'category': 'read_book', 'is_local': True}, - 'standalone_recently_opened': {'default': v'[]', 'category': 'read_book', 'is_local': True}, + 'standalone_recently_opened': {'default': v'[]', 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True}, 'user_color_schemes': {'default': {}, 'category': 'read_book'}, 'user_stylesheet': {'default': '', 'category': 'read_book', 'is_local': True}, 'word_actions': {'default': v'[]', 'category': 'read_book'}, @@ -69,14 +69,15 @@ all_settings = { 'net_search_url': {'default': 'https://google.com/search?q={q}', 'category': 'read_book'}, 'selection_bar_actions': {'default': v"['copy', 'lookup', 'highlight', 'remove_highlight', 'search_net', 'clear']", 'category': 'read_book'}, 'selection_bar_quick_highlights': {'default': v"[]", 'category': 'read_book'}, - 'skipped_dialogs': {'default': v'{}', 'category': 'read_book', 'is_local': True}, - 'tts': {'default': v'{}', 'category': 'read_book', 'is_local': True}, - 'tts_backend': {'default': v'{}', 'category': 'read_book', 'is_local': True}, + 'skipped_dialogs': {'default': v'{}', 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True}, + 'tts': {'default': v'{}', 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True}, + 'tts_backend': {'default': v'{}', 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True}, 'fullscreen_when_opening': {'default': 'auto', 'category': 'read_book', 'is_local': True}, 'book_search_mode': {'default': 'contains', 'category': 'read_book', 'is_local': True}, 'book_search_case_sensitive': {'default': False, 'category': 'read_book', 'is_local': True}, 'reverse_page_turn_zones': {'default': False, 'category': 'read_book', 'is_local': True}, 'gesture_overrides': {'default': {}, 'category': 'read_book'}, + 'current_profile': {'default': None, 'category': 'read_book', 'is_local': True, 'disallowed_in_profile': True}, } defaults = {} @@ -230,10 +231,20 @@ def get_subset_of_settings(sd, filter_func): metadata = all_settings[setting_name] is_set = curval is not ans if filter_func(setting_name, metadata, is_set): - ans[setting_name] = curval if is_set else metadata.default + ans[setting_name] = window.structuredClone(curval if is_set else metadata.default) return ans +def settings_for_reader_profile(sd): + + def filter_func(setting_name, metadata, is_set): + if not is_set or metadata.category is not 'read_book' or metadata.disallowed_in_profile: + return False + return True + + return get_subset_of_settings(sd, filter_func) + + default_interface_data = { 'username': None, 'output_format': 'EPUB', diff --git a/src/pyj/viewer-main.pyj b/src/pyj/viewer-main.pyj index 8e61ba3462..fab5a693a7 100644 --- a/src/pyj/viewer-main.pyj +++ b/src/pyj/viewer-main.pyj @@ -72,6 +72,32 @@ def get_file(book, name, proceed): xhr.send() +def profiles_recevied(proceed, end_type, xhr, ev): + end_type = workaround_qt_bug(xhr, end_type) + if end_type is 'abort': + return + if end_type is not 'load': + show_error(_('Failed to load profiles'), _( + 'Could not load viewer profiles with error: {}').format(xhr.error_html)) + return + if not xhr.responseType or xhr.responseType is 'text': + result = xhr.responseText + else if xhr.responseType is 'blob': + result = xhr.response + else: + show_error(_('Failed to load profiles'), _( + 'Could not load viewer profiles: unknown response type: {}').format(xhr.responseType)) + return + + proceed(result) + + +def get_all_profiles(proceed): + xhr = ajax('all-profiles/', profiles_recevied.bind(None, proceed), ok_code=0) + xhr.responseType = 'text' + xhr.send() + + def get_mathjax_files(proceed): proceed({}) @@ -326,6 +352,9 @@ if window is window.top: TRANSLATIONS_DATA = v'__TRANSLATIONS_DATA__' if TRANSLATIONS_DATA: install(TRANSLATIONS_DATA) + ui_operations.get_all_profiles = get_all_profiles + ui_operations.save_profile = def(name, settings): + to_python.save_profile(name, settings) ui_operations.get_file = get_file ui_operations.get_url_for_book_file_name = get_url_for_book_file_name ui_operations.get_mathjax_files = get_mathjax_files