diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index 3463738370..14fe4a5934 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -77,18 +77,26 @@ def get_data(name): return None, None -def background_image(): - ans = getattr(background_image, 'ans', None) - if ans is None: +@lru_cache(maxsize=4) +def background_image(encoded_fname=''): + if not encoded_fname: img_path = os.path.join(viewer_config_dir, 'bg-image.data') - if os.path.exists(img_path): + try: with open(img_path, 'rb') as f: data = f.read() - mt, data = data.split(b'|', 1) - else: - ans = b'image/jpeg', b'' - ans = background_image.ans = mt.decode('utf-8'), data - return ans + mt, data = data.split(b'|', 1) + mt = mt.decode() + return mt, data + except FileNotFoundError: + return 'image/jpeg', b'' + fname = bytes.fromhex(encoded_fname).decode() + img_path = os.path.join(viewer_config_dir, 'background-images', fname) + mt = guess_type(fname)[0] or 'image/jpeg' + try: + with open(img_path, 'rb') as f: + return mt, f.read() + except FileNotFoundError: + return mt, b'' @lru_cache(maxsize=2) @@ -161,10 +169,11 @@ class UrlSchemeHandler(QWebEngineUrlSchemeHandler): send_reply(rq, set_book_path.manifest_mime, data) elif name == 'reader-background': mt, data = background_image() - if data: - send_reply(rq, mt, data) - else: - rq.fail(QWebEngineUrlRequestJob.Error.UrlNotFound) + send_reply(rq, mt, data) if data else rq.fail(QWebEngineUrlRequestJob.Error.UrlNotFound) + elif name.startswith('reader-background-'): + 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.startswith('mathjax/'): handle_mathjax_request(rq, name) elif not name: @@ -693,14 +702,16 @@ class WebView(RestartingWebEngineView): self.execute_when_ready('show_home_page') def change_background_image(self, img_id): - files = choose_images(self, 'viewer-background-image', _('Choose background image'), formats=['png', 'gif', 'jpg', 'jpeg']) + files = choose_images(self, 'viewer-background-image', _('Choose background image'), formats=['png', 'gif', 'jpg', 'jpeg', 'webp']) if files: img = files[0] - with open(img, 'rb') as src, open(os.path.join(viewer_config_dir, 'bg-image.data'), 'wb') as dest: - dest.write(as_bytes(guess_type(img)[0] or 'image/jpeg') + b'|') - shutil.copyfileobj(src, dest) + d = os.path.join(viewer_config_dir, 'background-images') + os.makedirs(d, exist_ok=True) + fname = os.path.basename(img) + shutil.copyfile(img, os.path.join(d, fname)) background_image.ans = None - self.execute_when_ready('background_image_changed', img_id) + encoded = fname.encode().hex() + self.execute_when_ready('background_image_changed', img_id, f'{FAKE_PROTOCOL}://{FAKE_HOST}/reader-background-{encoded}') def goto_frac(self, frac): self.execute_when_ready('goto_frac', frac) diff --git a/src/calibre/srv/content.py b/src/calibre/srv/content.py index 90c37a4a69..1f06062b1d 100644 --- a/src/calibre/srv/content.py +++ b/src/calibre/srv/content.py @@ -30,7 +30,9 @@ from calibre.srv.routes import endpoint, json from calibre.srv.utils import get_db, get_use_roman, http_date from calibre.utils.config_base import tweaks from calibre.utils.date import timestampfromdt -from calibre.utils.filenames import ascii_filename, atomic_rename +from calibre.utils.filenames import ( + ascii_filename, atomic_rename, make_long_path_useable, +) from calibre.utils.img import image_from_data, scale_image from calibre.utils.localization import _ from calibre.utils.resources import get_image_path as I, get_path as P @@ -303,6 +305,19 @@ def icon(ctx, rd, which): return ans +@endpoint('/reader-background/{encoded_fname}', android_workaround=True) +def reader_background(ctx, rd, encoded_fname): + base = os.path.abspath(os.path.normapth(os.path.join(config_dir, 'viewer', 'background-images'))) + fname = bytes.fromhex(encoded_fname) + q = os.path.abspath(os.path.normpath(os.path.join(base, fname))) + if not q.startswith(base): + raise HTTPNotFound(f'Reader background {encoded_fname} not found') + try: + return share_open(make_long_path_useable(q), 'rb') + except FileNotFoundError: + raise HTTPNotFound(f'Reader background {encoded_fname} not found') + + @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/prefs/user_stylesheet.pyj b/src/pyj/read_book/prefs/user_stylesheet.pyj index 450f7072fa..77e04657c2 100644 --- a/src/pyj/read_book/prefs/user_stylesheet.pyj +++ b/src/pyj/read_book/prefs/user_stylesheet.pyj @@ -3,14 +3,16 @@ from __python__ import bound_methods, hash_literals from elementmaker import E -from gettext import gettext as _ +from ajax import absolute_path from book_list.globals import get_session_data from dom import ensure_id, unique_id +from encodings import hexlify +from gettext import gettext as _ from read_book.globals import runtime, ui_operations from read_book.prefs.utils import create_button_box from session import session_defaults -from viewer.constants import READER_BACKGROUND_URL +from viewer.constants import FAKE_HOST, FAKE_PROTOCOL from widgets import create_button BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' @@ -21,19 +23,33 @@ def change_background_image(img_id): def clear_image(img_id): - document.getElementById(img_id).src = BLANK + i = document.getElementById(img_id) + i.src = BLANK + i.dataset.url = '' + + +def modify_background_image_url_for_fetch(url): + if not url: + return BLANK + if runtime.is_standalone_viewer: + if url.startswith(f'{FAKE_PROTOCOL}:'): + return url + encoded = hexlify(url) + return f'{FAKE_PROTOCOL}://{FAKE_HOST}/reader-background-{encoded}' + if url.startswith(f'{FAKE_PROTOCOL}:'): + x = str.split(url, '/')[-1].partition('?')[0].partition('-')[2] + return absolute_path(f'reader-background/{x}') + return url def standalone_background_widget(sd): - if sd.get('background_image'): - src = READER_BACKGROUND_URL - else: - src = BLANK + url = sd.get('background_image') + src = modify_background_image_url_for_fetch(url) img_id = unique_id('bg-image') return E.div( style='display: flex; align-items: center', - E.div(E.img(src=src, id=img_id, class_='bg-image-preview', style='width: 75px; height: 75px; border: solid 1px')), + E.div(E.img(src=src, data_url=url, id=img_id, class_='bg-image-preview', style='width: 75px; height: 75px; border: solid 1px')), E.div('\xa0', style='margin: 0.5rem'), create_button(_('Change image'), action=change_background_image.bind(None, img_id)), E.div('\xa0', style='margin: 0.5rem'), @@ -77,7 +93,8 @@ def restore_defaults(): container = document.getElementById(create_user_stylesheet_panel.container_id) container.querySelector('[name=user-stylesheet]').value = '' if runtime.is_standalone_viewer: - clear_image(container.querySelector('img').id) + i = container.querySelector('img') + clear_image(i.id) else: container.querySelector('[name=background_image]').value = '' container.querySelector('select[name=background_image_style]').value = session_defaults().background_image_style @@ -132,8 +149,8 @@ def commit_user_stylesheet(onchange, container): sd.set('user_stylesheet', val) changed = True if runtime.is_standalone_viewer: - bg_image = container.querySelector('img.bg-image-preview').src - if bg_image is BLANK: + bg_image = container.querySelector('img.bg-image-preview').dataset.url + if bg_image is BLANK or not bg_image: bg_image = None else: bg_image = container.querySelector('input[name=background_image]').value diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 3d8d86858b..4dc0e7a66f 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -33,6 +33,7 @@ from read_book.prefs.head_foot import render_head_foot from read_book.prefs.scrolling import ( MIN_SCROLL_SPEED_AUTO as SCROLL_SPEED_STEP, change_scroll_speed ) +from read_book.prefs.user_stylesheet import modify_background_image_url_for_fetch from read_book.read_aloud import ReadAloud from read_book.read_audio_ebook import ReadAudioEbook from read_book.resources import load_resources @@ -49,7 +50,6 @@ from utils import ( default_context_menu_should_be_allowed, html_escape, is_ios, parse_url_params, safe_set_inner_html, username_key ) -from viewer.constants import READER_BACKGROUND_URL add_extra_css(def(): sel = '.book-side-margin' @@ -851,7 +851,7 @@ class View: iframe.style.backgroundColor = ans.background or 'white' bg_image = sd.get('background_image') if bg_image: - iframe.style.backgroundImage = f'url({READER_BACKGROUND_URL}?{Date().getTime()})' if runtime.is_standalone_viewer else f'url({bg_image})' + iframe.style.backgroundImage = f'url({modify_background_image_url_for_fetch(bg_image)})' else: iframe.style.backgroundImage = 'none' if sd.get('background_image_style') is 'scaled': diff --git a/src/pyj/viewer-main.pyj b/src/pyj/viewer-main.pyj index c1729e912f..8e61ba3462 100644 --- a/src/pyj/viewer-main.pyj +++ b/src/pyj/viewer-main.pyj @@ -26,7 +26,7 @@ from read_book.prefs.head_foot import set_time_formatter from read_book.view import View from session import local_storage, session_defaults, default_interface_data from utils import debounce, encode_query_with_path, parse_url_params -from viewer.constants import FAKE_HOST, FAKE_PROTOCOL, READER_BACKGROUND_URL +from viewer.constants import FAKE_HOST, FAKE_PROTOCOL runtime.is_standalone_viewer = True runtime.FAKE_HOST = FAKE_HOST @@ -267,10 +267,11 @@ def goto_frac(frac): @from_python -def background_image_changed(img_id): +def background_image_changed(img_id, url): img = document.getElementById(img_id) if img: - img.src = READER_BACKGROUND_URL + '?' + Date().getTime() + img.src = f'{url}?{Date().getTime()}' + img.dataset.url = url @from_python diff --git a/src/pyj/viewer/constants.pyj b/src/pyj/viewer/constants.pyj index 5ee296b986..aaabf5aefe 100644 --- a/src/pyj/viewer/constants.pyj +++ b/src/pyj/viewer/constants.pyj @@ -5,4 +5,3 @@ from __python__ import bound_methods, hash_literals FAKE_PROTOCOL = '__FAKE_PROTOCOL__' FAKE_HOST = '__FAKE_HOST__' -READER_BACKGROUND_URL = f'{FAKE_PROTOCOL}://{FAKE_HOST}/reader-background'