E-book viewer: Handle links to missing internal files with an error popup rather than becoming non-functional

This commit is contained in:
Kovid Goyal 2025-05-05 12:41:55 +05:30
parent c0f15325ed
commit 9d81359d8a
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 30 additions and 17 deletions

View File

@ -13,7 +13,7 @@ from itertools import count
from lxml.etree import Comment
from calibre import detect_ncpus, force_unicode, prepare_string_for_xml
from calibre import detect_ncpus, prepare_string_for_xml
from calibre.customize.ui import plugin_for_input_format
from calibre.ebooks.oeb.base import EPUB, OEB_DOCS, OEB_STYLES, OPF, SMIL, XHTML, XHTML_NS, XLINK, rewrite_links, urlunquote
from calibre.ebooks.oeb.base import XPath as _XPath
@ -33,7 +33,7 @@ from polyglot.binary import as_base64_unicode as encode_component
from polyglot.binary import from_base64_bytes
from polyglot.binary import from_base64_unicode as decode_component
from polyglot.builtins import as_bytes
from polyglot.urllib import quote, urlparse
from polyglot.urllib import urlparse
try:
from calibre_extensions.speedup import get_num_of_significant_chars
@ -98,9 +98,7 @@ def create_link_replacer(container, link_uid, changed):
frag = urlunquote(frag)
url = resource_template.format(encode_url(name, frag))
else:
if isinstance(name, str):
name = name.encode('utf-8')
url = 'missing:' + force_unicode(quote(name), 'utf-8')
url = 'missing:' + name
changed.add(base)
return url
@ -475,7 +473,8 @@ def transform_html(container, name, virtualize_resources, link_uid, link_to_map,
href = link_replacer(name, href)
elif attr in a.attrib:
a.set(attr, 'javascript:void(0)')
if href and href.startswith(link_uid):
if href:
if href.startswith(link_uid):
a.set(attr, 'javascript:void(0)')
parts = href.split('|')
if len(parts) > 1:
@ -483,6 +482,9 @@ def transform_html(container, name, virtualize_resources, link_uid, link_to_map,
lname, lfrag = parts[0], parts[1]
link_to_map.setdefault(lname, {}).setdefault(lfrag or '', set()).add(name)
a.set('data-' + link_uid, json.dumps({'name':lname, 'frag':lfrag}, ensure_ascii=False))
elif href.startswith('missing:'):
a.set(attr, 'javascript:void(0)')
a.set('data-' + link_uid, json.dumps({'name':href[len('missing:'):], 'frag':'', 'missing': True}, ensure_ascii=False))
for a in link_xpath(root):
handle_link(a)
@ -519,6 +521,10 @@ def virtualize_html(container, name, link_uid, link_to_map, virtualized_names):
link_to_map.setdefault(lname, {}).setdefault(lfrag or '', set()).add(name)
a.set('data-' + link_uid, json.dumps({'name':lname, 'frag':lfrag}, ensure_ascii=False))
elif href:
if href.startswith('missing:'):
a.set(attr, 'javascript:void(0)')
a.set('data-' + link_uid, json.dumps({'name':href[len('missing:'):], 'frag':'', 'missing': True}, ensure_ascii=False))
else:
a.set('target', '_blank')
a.set('rel', 'noopener noreferrer')
elif attr in a.attrib:
@ -978,7 +984,7 @@ def develop(max_workers=1, wait_for_input=True):
with TemporaryDirectory() as tdir:
render(
path, tdir, serialize_metadata=True,
extract_annotations=True, virtualize_resources=True, max_workers=max_workers
extract_annotations=True, virtualize_resources=False, max_workers=max_workers
)
print('Extracted to:', tdir)
if wait_for_input:

View File

@ -745,6 +745,9 @@ class IframeBoss:
except:
print('WARNING: Failed to parse link data {}, ignoring'.format(evt.currentTarget?.getAttribute?(self.link_attr)))
return
if data.missing:
self.send_message('link_to_missing_activated', name=data.name)
else:
self.activate_link(data.name, data.frag, evt.currentTarget)
def activate_link(self, name, frag, target_elem):

View File

@ -254,6 +254,7 @@ class View:
'report_cfi': self.on_report_cfi,
'request_size': self.on_request_size,
'scroll_to_anchor': self.on_scroll_to_anchor,
'link_to_missing_activated': self.on_link_to_missing_activated,
'selectionchange': self.on_selection_change,
'update_selection_position': self.update_selection_position,
'columns_per_screen_changed': self.on_columns_per_screen_changed,
@ -1122,6 +1123,9 @@ class View:
frag = item.frag or ''
self.show_name(name, initial_position={'type':'pagelist_ref', 'anchor':frag, 'replace_history':False})
def on_link_to_missing_activated(self, data):
ui_operations.show_error(_('Invalid link'), _('This link points to the file {} which does not exist in the book').format(data.name))
def on_scroll_to_anchor(self, data):
self.show_name(data.name, initial_position={'type':'anchor', 'anchor':data.frag, 'replace_history':False})