Change how background image works in preparation for profiles

Allow multiple background images and allow settings from browser and
desktop readers to work with each other
This commit is contained in:
Kovid Goyal 2024-02-15 09:35:09 +05:30
parent 558992008a
commit 3cbcd0acc9
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 79 additions and 36 deletions

View File

@ -77,18 +77,26 @@ def get_data(name):
return None, None return None, None
def background_image(): @lru_cache(maxsize=4)
ans = getattr(background_image, 'ans', None) def background_image(encoded_fname=''):
if ans is None: if not encoded_fname:
img_path = os.path.join(viewer_config_dir, 'bg-image.data') 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: with open(img_path, 'rb') as f:
data = f.read() data = f.read()
mt, data = data.split(b'|', 1) mt, data = data.split(b'|', 1)
else: mt = mt.decode()
ans = b'image/jpeg', b'' return mt, data
ans = background_image.ans = mt.decode('utf-8'), data except FileNotFoundError:
return ans 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) @lru_cache(maxsize=2)
@ -161,10 +169,11 @@ class UrlSchemeHandler(QWebEngineUrlSchemeHandler):
send_reply(rq, set_book_path.manifest_mime, data) send_reply(rq, set_book_path.manifest_mime, data)
elif name == 'reader-background': elif name == 'reader-background':
mt, data = background_image() mt, data = background_image()
if data: send_reply(rq, mt, data) if data else rq.fail(QWebEngineUrlRequestJob.Error.UrlNotFound)
send_reply(rq, mt, data) elif name.startswith('reader-background-'):
else: encoded_fname = name[len('reader-background-'):]
rq.fail(QWebEngineUrlRequestJob.Error.UrlNotFound) mt, data = background_image(encoded_fname)
send_reply(rq, mt, data) if data else rq.fail(QWebEngineUrlRequestJob.Error.UrlNotFound)
elif name.startswith('mathjax/'): elif name.startswith('mathjax/'):
handle_mathjax_request(rq, name) handle_mathjax_request(rq, name)
elif not name: elif not name:
@ -693,14 +702,16 @@ class WebView(RestartingWebEngineView):
self.execute_when_ready('show_home_page') self.execute_when_ready('show_home_page')
def change_background_image(self, img_id): 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: if files:
img = files[0] img = files[0]
with open(img, 'rb') as src, open(os.path.join(viewer_config_dir, 'bg-image.data'), 'wb') as dest: d = os.path.join(viewer_config_dir, 'background-images')
dest.write(as_bytes(guess_type(img)[0] or 'image/jpeg') + b'|') os.makedirs(d, exist_ok=True)
shutil.copyfileobj(src, dest) fname = os.path.basename(img)
shutil.copyfile(img, os.path.join(d, fname))
background_image.ans = None 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): def goto_frac(self, frac):
self.execute_when_ready('goto_frac', frac) self.execute_when_ready('goto_frac', frac)

View File

@ -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.srv.utils import get_db, get_use_roman, http_date
from calibre.utils.config_base import tweaks from calibre.utils.config_base import tweaks
from calibre.utils.date import timestampfromdt 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.img import image_from_data, scale_image
from calibre.utils.localization import _ from calibre.utils.localization import _
from calibre.utils.resources import get_image_path as I, get_path as P 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 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) @endpoint('/get/{what}/{book_id}/{library_id=None}', android_workaround=True)
def get(ctx, rd, what, book_id, library_id): def get(ctx, rd, what, book_id, library_id):
book_id, rest = book_id.partition('_')[::2] book_id, rest = book_id.partition('_')[::2]

View File

@ -3,14 +3,16 @@
from __python__ import bound_methods, hash_literals from __python__ import bound_methods, hash_literals
from elementmaker import E from elementmaker import E
from gettext import gettext as _
from ajax import absolute_path
from book_list.globals import get_session_data from book_list.globals import get_session_data
from dom import ensure_id, unique_id 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.globals import runtime, ui_operations
from read_book.prefs.utils import create_button_box from read_book.prefs.utils import create_button_box
from session import session_defaults 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 from widgets import create_button
BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
@ -21,19 +23,33 @@ def change_background_image(img_id):
def clear_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): def standalone_background_widget(sd):
if sd.get('background_image'): url = sd.get('background_image')
src = READER_BACKGROUND_URL src = modify_background_image_url_for_fetch(url)
else:
src = BLANK
img_id = unique_id('bg-image') img_id = unique_id('bg-image')
return E.div( return E.div(
style='display: flex; align-items: center', 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'), E.div('\xa0', style='margin: 0.5rem'),
create_button(_('Change image'), action=change_background_image.bind(None, img_id)), create_button(_('Change image'), action=change_background_image.bind(None, img_id)),
E.div('\xa0', style='margin: 0.5rem'), E.div('\xa0', style='margin: 0.5rem'),
@ -77,7 +93,8 @@ def restore_defaults():
container = document.getElementById(create_user_stylesheet_panel.container_id) container = document.getElementById(create_user_stylesheet_panel.container_id)
container.querySelector('[name=user-stylesheet]').value = '' container.querySelector('[name=user-stylesheet]').value = ''
if runtime.is_standalone_viewer: if runtime.is_standalone_viewer:
clear_image(container.querySelector('img').id) i = container.querySelector('img')
clear_image(i.id)
else: else:
container.querySelector('[name=background_image]').value = '' container.querySelector('[name=background_image]').value = ''
container.querySelector('select[name=background_image_style]').value = session_defaults().background_image_style 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) sd.set('user_stylesheet', val)
changed = True changed = True
if runtime.is_standalone_viewer: if runtime.is_standalone_viewer:
bg_image = container.querySelector('img.bg-image-preview').src bg_image = container.querySelector('img.bg-image-preview').dataset.url
if bg_image is BLANK: if bg_image is BLANK or not bg_image:
bg_image = None bg_image = None
else: else:
bg_image = container.querySelector('input[name=background_image]').value bg_image = container.querySelector('input[name=background_image]').value

View File

@ -33,6 +33,7 @@ from read_book.prefs.head_foot import render_head_foot
from read_book.prefs.scrolling import ( from read_book.prefs.scrolling import (
MIN_SCROLL_SPEED_AUTO as SCROLL_SPEED_STEP, change_scroll_speed 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_aloud import ReadAloud
from read_book.read_audio_ebook import ReadAudioEbook from read_book.read_audio_ebook import ReadAudioEbook
from read_book.resources import load_resources 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, default_context_menu_should_be_allowed, html_escape, is_ios, parse_url_params,
safe_set_inner_html, username_key safe_set_inner_html, username_key
) )
from viewer.constants import READER_BACKGROUND_URL
add_extra_css(def(): add_extra_css(def():
sel = '.book-side-margin' sel = '.book-side-margin'
@ -851,7 +851,7 @@ class View:
iframe.style.backgroundColor = ans.background or 'white' iframe.style.backgroundColor = ans.background or 'white'
bg_image = sd.get('background_image') bg_image = sd.get('background_image')
if bg_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: else:
iframe.style.backgroundImage = 'none' iframe.style.backgroundImage = 'none'
if sd.get('background_image_style') is 'scaled': if sd.get('background_image_style') is 'scaled':

View File

@ -26,7 +26,7 @@ from read_book.prefs.head_foot import set_time_formatter
from read_book.view import View from read_book.view import View
from session import local_storage, session_defaults, default_interface_data from session import local_storage, session_defaults, default_interface_data
from utils import debounce, encode_query_with_path, parse_url_params 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.is_standalone_viewer = True
runtime.FAKE_HOST = FAKE_HOST runtime.FAKE_HOST = FAKE_HOST
@ -267,10 +267,11 @@ def goto_frac(frac):
@from_python @from_python
def background_image_changed(img_id): def background_image_changed(img_id, url):
img = document.getElementById(img_id) img = document.getElementById(img_id)
if img: if img:
img.src = READER_BACKGROUND_URL + '?' + Date().getTime() img.src = f'{url}?{Date().getTime()}'
img.dataset.url = url
@from_python @from_python

View File

@ -5,4 +5,3 @@ from __python__ import bound_methods, hash_literals
FAKE_PROTOCOL = '__FAKE_PROTOCOL__' FAKE_PROTOCOL = '__FAKE_PROTOCOL__'
FAKE_HOST = '__FAKE_HOST__' FAKE_HOST = '__FAKE_HOST__'
READER_BACKGROUND_URL = f'{FAKE_PROTOCOL}://{FAKE_HOST}/reader-background'