From 9bc695c84373405d84e2029e1f0d4c328da2ae41 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 21 Feb 2024 22:57:08 +0530 Subject: [PATCH] Implement profiles for the Content server viewer Fixes #1979022 [[Feature Request] Import/Export setting file for Content Server Viewer](https://bugs.launchpad.net/calibre/+bug/1979022) --- src/calibre/gui2/viewer/config.py | 6 +----- src/calibre/srv/content.py | 26 ++++++++++++++++++++++++++ src/pyj/read_book/overlay.pyj | 3 +++ src/pyj/read_book/ui.pyj | 22 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/viewer/config.py b/src/calibre/gui2/viewer/config.py index afb41d9618..5439cddad4 100644 --- a/src/calibre/gui2/viewer/config.py +++ b/src/calibre/gui2/viewer/config.py @@ -89,23 +89,19 @@ def expand_profile_user_names(user_names): return user_names -def load_viewer_profiles(*user_names: str, as_json_string=False): +def load_viewer_profiles(*user_names: str): 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 diff --git a/src/calibre/srv/content.py b/src/calibre/srv/content.py index 1f06062b1d..8730f8b1ca 100644 --- a/src/calibre/srv/content.py +++ b/src/calibre/srv/content.py @@ -318,6 +318,32 @@ def reader_background(ctx, rd, encoded_fname): raise HTTPNotFound(f'Reader background {encoded_fname} not found') +@endpoint('/reader-profiles/get-all', postprocess=json) +def get_all_reader_profiles(ctx, rd): + from calibre.gui2.viewer.config import load_viewer_profiles + which = 'user:' + if rd.username: + which += rd.username + return load_viewer_profiles(which) + + +@endpoint('/reader-profiles/save', methods={'POST'}, postprocess=json) +def save_reader_profile(ctx, rd): + try: + data = load_json_file(rd.request_body_file) + name, profile = data['name'], data['profile'] + if not isinstance(profile, dict): + raise TypeError('profile must be a dict') + except Exception as err: + raise HTTPBadRequest(f'Invalid query: {err}') + from calibre.gui2.viewer.config import save_viewer_profile + which = 'user:' + if rd.username: + which += rd.username + save_viewer_profile(name, profile, which) + return True + + @endpoint('/get/{what}/{book_id}/{library_id=None}', android_workaround=True) def get(ctx, rd, what, book_id, library_id): book_id, rest = book_id.partition('_')[::2] diff --git a/src/pyj/read_book/overlay.pyj b/src/pyj/read_book/overlay.pyj index 98c48e5db4..d881eb1fb5 100644 --- a/src/pyj/read_book/overlay.pyj +++ b/src/pyj/read_book/overlay.pyj @@ -564,6 +564,9 @@ class ProfilesOverlay(PrefsOverlay): def create_panel_impl(self, container): return create_profiles_panel(container, self.overlay.hide_current_panel, self.on_change) + def handle_escape(self): + self.overlay.hide_current_panel() + # }}} class FontSizeOverlay: # {{{ diff --git a/src/pyj/read_book/ui.pyj b/src/pyj/read_book/ui.pyj index 7f9c516548..82f80b390c 100644 --- a/src/pyj/read_book/ui.pyj +++ b/src/pyj/read_book/ui.pyj @@ -136,6 +136,28 @@ class ReadUI: error_dialog(_('Could not copy to clipboard'), _('No permission to write to clipboard')) ) + ui_operations.get_all_profiles = def(proceed): + ajax('reader-profiles/get-all', def(end_type, xhr, ev): + if end_type is not 'load': + if end_type is 'abort': return + return self.show_error(_('Failed to load profiles'), _( + 'Could not load profiles: with error: {}').format(xhr.error_html)) + try: + result = JSON.parse(xhr.responseText) + except Exception: + return self.show_error(_('Failed to parse profiles'), _('Unparseable data received for profiles')) + proceed(result) + ).send() + + ui_operations.save_profile = def(profile_name, profile, proceed): + ajax_send('reader-profiles/save', {'name': profile_name, 'profile': profile}, def(end_type, xhr, ev): + if end_type is not 'load': + if end_type is 'abort': return + return self.show_error(_('Failed to load profiles'), _( + 'Could not load profiles: with error: {}').format(xhr.error_html)) + proceed() + ) + def on_resize(self): self.view.on_resize()