Work on reafactoring read_book module to make it useable in standalone viewer

This commit is contained in:
Kovid Goyal 2018-09-04 10:52:09 +05:30
parent 953734320b
commit 9b3d5d486b
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
10 changed files with 91 additions and 37 deletions

View File

@ -17,7 +17,9 @@ from io import BytesIO
from threading import Thread, local from threading import Thread, local
from calibre import force_unicode from calibre import force_unicode
from calibre.constants import __appname__, __version__, cache_dir from calibre.constants import (
FAKE_HOST, FAKE_PROTOCOL, __appname__, __version__, cache_dir
)
from calibre.utils.filenames import atomic_rename from calibre.utils.filenames import atomic_rename
from calibre.utils.terminal import ANSIStream from calibre.utils.terminal import ANSIStream
from duktape import Context, JSError, to_python from duktape import Context, JSError, to_python
@ -226,7 +228,10 @@ def compile_viewer():
rapydscript_dir = os.path.join(base, 'src', 'pyj') rapydscript_dir = os.path.join(base, 'src', 'pyj')
fname = os.path.join(rapydscript_dir, 'viewer-main.pyj') fname = os.path.join(rapydscript_dir, 'viewer-main.pyj')
with lopen(fname, 'rb') as f: with lopen(fname, 'rb') as f:
js = compile_fast(f.read(), fname, js_version=6).replace('__SPECIAL_TITLE__', special_title, 1) js = compile_fast(f.read(), fname, js_version=6).replace(
'__SPECIAL_TITLE__', special_title, 1).replace(
'__FAKE_PROTOCOL__', FAKE_PROTOCOL, 1).replace(
'__FAKE_HOST__', FAKE_HOST, 1)
base = os.path.join(base, 'resources') base = os.path.join(base, 'resources')
atomic_write(base, 'viewer.js', js) atomic_write(base, 'viewer.js', js)

View File

@ -81,13 +81,16 @@ class IframeWrapper:
def create_srcdoc(self): def create_srcdoc(self):
r = /__([A-Z][A-Z_0-9]*[A-Z0-9])__/g r = /__([A-Z][A-Z_0-9]*[A-Z0-9])__/g
data = { if self.entry_point:
'BS': self.bootstrap_text, data = {
'SCRIPT': iframe_js(), 'BS': self.bootstrap_text,
'FONT': get_font_family(), 'SCRIPT': iframe_js(),
'ENTRY_POINT': self.entry_point, 'FONT': get_font_family(),
} 'ENTRY_POINT': self.entry_point,
self.iframe.srcdoc = LOADING_DOC.replace(r, def(match, field): return data[field];) }
self.iframe.srcdoc = LOADING_DOC.replace(r, def(match, field): return data[field];)
else:
self.iframe.srcdoc = '<div>\xa0</div>'
self.srcdoc_created = True self.srcdoc_created = True
def init(self): def init(self):

View File

@ -60,3 +60,16 @@ register_callback(def():
scheme = default_color_schemes[key] scheme = default_color_schemes[key]
scheme.name = gt(scheme.name) scheme.name = gt(scheme.name)
) )
runtime = {
'is_standalone_viewer': False
}
ui_operations = {
'get_file': None,
'get_mathjax_files': None,
'update_url_state': None,
'update_last_read_time': None,
'show_error': None,
}

View File

@ -6,6 +6,7 @@ from elementmaker import E
from encodings import base64decode, utf8_decode from encodings import base64decode, utf8_decode
from dom import clear from dom import clear
from read_book.globals import ui_operations
JSON_XHTML_MIMETYPE = 'application/calibre+xhtml+json' JSON_XHTML_MIMETYPE = 'application/calibre+xhtml+json'
@ -19,7 +20,7 @@ def decode_url(x):
def create_link_pat(book): def create_link_pat(book):
return RegExp(book.manifest.link_uid + r'\|([^|]+)\|', 'g') return RegExp(book.manifest.link_uid + r'\|([^|]+)\|', 'g')
def load_resources(db, book, root_name, previous_resources, proceed): def load_resources(book, root_name, previous_resources, proceed):
ans = Object.create(None) ans = Object.create(None)
pending_resources = v'[root_name]' pending_resources = v'[root_name]'
link_pat = create_link_pat(book) link_pat = create_link_pat(book)
@ -29,7 +30,7 @@ def load_resources(db, book, root_name, previous_resources, proceed):
for k in previous_resources: for k in previous_resources:
v'delete previous_resources[k]' v'delete previous_resources[k]'
if book.manifest.files[root_name].has_maths: if book.manifest.files[root_name].has_maths:
return load_mathjax(db, book, ans, proceed) return load_mathjax(book, ans, proceed)
return proceed(ans) return proceed(ans)
name = pending_resources.shift() name = pending_resources.shift()
if ans[name]: if ans[name]:
@ -39,7 +40,7 @@ def load_resources(db, book, root_name, previous_resources, proceed):
if jstype(data[0]) is 'string': if jstype(data[0]) is 'string':
find_virtualized_resources(data[0]) find_virtualized_resources(data[0])
return setTimeout(do_one, 0) return setTimeout(do_one, 0)
db.get_file(book, name, got_one) ui_operations.get_file(book, name, got_one)
def got_one(data, name, mimetype): def got_one(data, name, mimetype):
if False and name is book.manifest.title_page_name: if False and name is book.manifest.title_page_name:
@ -68,9 +69,9 @@ def load_resources(db, book, root_name, previous_resources, proceed):
mathjax_data = None mathjax_data = None
def load_mathjax(db, book, resource_data, proceed): def load_mathjax(book, resource_data, proceed):
if mathjax_data is None: if mathjax_data is None:
db.get_mathjax_files(def(data): ui_operations.get_mathjax_files(def(data):
nonlocal mathjax_data nonlocal mathjax_data
mathjax_data = data mathjax_data = data
resource_data['..mathjax-files..'] = data resource_data['..mathjax-files..'] = data

View File

@ -1,13 +1,15 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import hash_literals, bound_methods from __python__ import bound_methods, hash_literals
from elementmaker import E
from gettext import gettext as _
from book_list.theme import get_color
from complete import create_search_bar from complete import create_search_bar
from dom import add_extra_css, build_rule, svgicon from dom import add_extra_css, build_rule, svgicon
from keycodes import get_key from keycodes import get_key
from elementmaker import E from read_book.globals import ui_operations
from gettext import gettext as _
from book_list.theme import get_color
from read_book.resources import text_from_serialized_html from read_book.resources import text_from_serialized_html
CLASS_NAME = 'book-search-container' CLASS_NAME = 'book-search-container'
@ -84,7 +86,7 @@ def find_in_serialized_html(data, text):
return haystack.toLowerCase().indexOf(text) > -1 return haystack.toLowerCase().indexOf(text) > -1
def find_in_spine(names, book, db, text, proceed): def find_in_spine(names, book, text, proceed):
text = text.toLowerCase() text = text.toLowerCase()
def got_one(data, name, mimetype): def got_one(data, name, mimetype):
@ -96,7 +98,7 @@ def find_in_spine(names, book, db, text, proceed):
def do_one(): def do_one():
name = names.shift() name = names.shift()
if name: if name:
db.get_file(book, name, got_one) ui_operations.get_file(book, name, got_one)
else: else:
proceed(None) proceed(None)

View File

@ -9,14 +9,13 @@ from gettext import gettext as _
from ajax import ajax from ajax import ajax
from book_list.constants import read_book_container_id from book_list.constants import read_book_container_id
from book_list.library_data import ( from book_list.library_data import current_library_id, library_data
current_library_id, library_data from book_list.router import home, push_state, read_book_mode, update_window_title
)
from book_list.ui import show_panel from book_list.ui import show_panel
from book_list.router import update_window_title, home
from dom import clear from dom import clear
from modals import create_simple_dialog_markup, error_dialog from modals import create_simple_dialog_markup, error_dialog
from read_book.db import get_db from read_book.db import get_db
from read_book.globals import ui_operations
from read_book.view import View from read_book.view import View
from utils import debounce, human_readable from utils import debounce, human_readable
from widgets import create_button from widgets import create_button
@ -53,9 +52,14 @@ class ReadUI:
container.appendChild(E.div( container.appendChild(E.div(
id=self.display_id, style='display:none', id=self.display_id, style='display:none',
)) ))
self.view = View(container.lastChild, self) self.view = View(container.lastChild)
window.addEventListener('resize', debounce(self.on_resize.bind(self), 250)) window.addEventListener('resize', debounce(self.on_resize.bind(self), 250))
self.db = get_db(self.db_initialized.bind(self), self.show_error.bind(self)) self.db = get_db(self.db_initialized.bind(self), self.show_error.bind(self))
ui_operations.get_file = self.db.get_file
ui_operations.get_mathjax_files = self.db.get_mathjax_files
ui_operations.update_url_state = self.update_url_state.bind(self)
ui_operations.update_last_read_time = self.db.update_last_read_time
ui_operations.show_error = self.show_error.bind(self)
def on_resize(self): def on_resize(self):
self.view.on_resize() self.view.on_resize()
@ -448,3 +452,6 @@ class ReadUI:
self.view.goto_bookpos(current_query.bookpos) self.view.goto_bookpos(current_query.bookpos)
else: else:
self.load_book(current_query.library_id, int(current_query.book_id), current_query.fmt, library_data.metadata[current_query.book_id]) self.load_book(current_query.library_id, int(current_query.book_id), current_query.fmt, library_data.metadata[current_query.book_id])
def update_url_state(self, replace):
push_state(self.url_data, replace=replace, mode=read_book_mode)

View File

@ -9,13 +9,14 @@ import read_book.iframe # noqa
from ajax import ajax_send from ajax import ajax_send
from book_list.book_details import CLASS_NAME as BD_CLASS_NAME, render_metadata from book_list.book_details import CLASS_NAME as BD_CLASS_NAME, render_metadata
from book_list.globals import get_session_data from book_list.globals import get_session_data
from book_list.router import push_state, read_book_mode
from book_list.theme import get_color from book_list.theme import get_color
from dom import add_extra_css, build_rule, clear, set_css, svgicon, unique_id from dom import add_extra_css, build_rule, clear, set_css, svgicon, unique_id
from iframe_comm import IframeWrapper from iframe_comm import IframeWrapper
from modals import error_dialog, warning_dialog from modals import error_dialog, warning_dialog
from read_book.content_popup import ContentPopupOverlay from read_book.content_popup import ContentPopupOverlay
from read_book.globals import current_book, set_current_spine_item from read_book.globals import (
current_book, runtime, set_current_spine_item, ui_operations
)
from read_book.goto import get_next_section from read_book.goto import get_next_section
from read_book.overlay import Overlay from read_book.overlay import Overlay
from read_book.prefs.colors import resolve_color_scheme from read_book.prefs.colors import resolve_color_scheme
@ -115,8 +116,7 @@ def margin_elem(sd, which, id, onclick):
class View: class View:
def __init__(self, container, ui): def __init__(self, container):
self.ui = ui
self.timers = Timers() self.timers = Timers()
self.loaded_resources = {} self.loaded_resources = {}
self.current_progress_frac = self.current_file_progress_frac = 0 self.current_progress_frac = self.current_file_progress_frac = 0
@ -167,7 +167,8 @@ class View:
'print': self.on_print, 'print': self.on_print,
'human_scroll': self.on_human_scroll, 'human_scroll': self.on_human_scroll,
} }
self.iframe_wrapper = IframeWrapper(handlers, document.getElementById(iframe_id), 'read_book.iframe', _('Bootstrapping book reader...')) entry_point = None if runtime.is_standalone_viewer else 'read_book.iframe'
self.iframe_wrapper = IframeWrapper(handlers, document.getElementById(iframe_id), entry_point, _('Bootstrapping book reader...'))
self.search_overlay = SearchOverlay(self) self.search_overlay = SearchOverlay(self)
self.content_popup_overlay = ContentPopupOverlay(self) self.content_popup_overlay = ContentPopupOverlay(self)
self.overlay = Overlay(self) self.overlay = Overlay(self)
@ -245,7 +246,7 @@ class View:
for items in item_groups: for items in item_groups:
for i in items: for i in items:
names.push(spine[i]) names.push(spine[i])
find_in_spine(names, self.book, self.ui.db, data.text, def(found_in): find_in_spine(names, self.book, data.text, def(found_in):
if found_in: if found_in:
self.show_name(found_in, initial_position={'type':'search', 'search_data':data, 'replace_history':True}) self.show_name(found_in, initial_position={'type':'search', 'search_data':data, 'replace_history':True})
else: else:
@ -322,7 +323,7 @@ class View:
self.show_spine_item_stage2(data) self.show_spine_item_stage2(data)
def on_iframe_error(self, data): def on_iframe_error(self, data):
self.ui.show_error((data.title or _('There was an error processing the book')), data.msg, data.details) ui_operations.show_error((data.title or _('There was an error processing the book')), data.msg, data.details)
def get_color_scheme(self, apply_to_margins): def get_color_scheme(self, apply_to_margins):
ans = resolve_color_scheme() ans = resolve_color_scheme()
@ -376,7 +377,8 @@ class View:
self.content_popup_overlay.loaded_resources = {} self.content_popup_overlay.loaded_resources = {}
self.timers.start_book(book) self.timers.start_book(book)
self.book = current_book.book = book self.book = current_book.book = book
self.ui.db.update_last_read_time(book) if ui_operations.update_last_read_time:
ui_operations.update_last_read_time(book)
pos = {'replace_history':True} pos = {'replace_history':True}
unkey = username_key(get_interface_data().username) unkey = username_key(get_interface_data().username)
name = book.manifest.spine[0] name = book.manifest.spine[0]
@ -431,7 +433,7 @@ class View:
def cb(resource_data): def cb(resource_data):
self.loaded_resources = resource_data self.loaded_resources = resource_data
done_callback(resource_data) done_callback(resource_data)
load_resources(self.ui.db, self.book, name, self.loaded_resources, cb) load_resources(self.book, name, self.loaded_resources, cb)
def goto_doc_boundary(self, start): def goto_doc_boundary(self, start):
name = self.book.manifest.spine[0 if start else self.book.manifest.spine.length - 1] name = self.book.manifest.spine[0 if start else self.book.manifest.spine.length - 1]
@ -495,13 +497,14 @@ class View:
# See https://bugs.chromium.org/p/chromium/issues/detail?id=404315 # See https://bugs.chromium.org/p/chromium/issues/detail?id=404315
return return
self.currently_showing.bookpos = data.cfi self.currently_showing.bookpos = data.cfi
push_state(self.ui.url_data, replace=data.replace_history, mode=read_book_mode) ui_operations.update_url_state(data.replace_history)
username = get_interface_data().username username = get_interface_data().username
unkey = username_key(username) unkey = username_key(username)
if not self.book.last_read_position: if not self.book.last_read_position:
self.book.last_read_position = {} self.book.last_read_position = {}
self.book.last_read_position[unkey] = data.cfi self.book.last_read_position[unkey] = data.cfi
self.ui.db.update_last_read_time(self.book) if ui_operations.update_last_read_time:
ui_operations.update_last_read_time(self.book)
lrd = {'device':get_device_uuid(), 'cfi':data.cfi, 'pos_frac':data.progress_frac} lrd = {'device':get_device_uuid(), 'cfi':data.cfi, 'pos_frac':data.progress_frac}
self.current_progress_frac = data.progress_frac self.current_progress_frac = data.progress_frac
self.current_file_progress_frac = data.file_progress_frac self.current_file_progress_frac = data.file_progress_frac

View File

@ -2,4 +2,17 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals from __python__ import bound_methods, hash_literals
print('11111111111111', document.location.href)
from elementmaker import E
from read_book.globals import runtime
def container_div(id):
return E.div(id=id, style='margin: 0; padding: 0; display: none')
runtime.is_standalone_viewer = True
if window is window.top:
# main
document.body.appendChild(E.iframe(srcdoc="<p>hello"))
else:
# iframe
pass

View File

View File

@ -0,0 +1,7 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals
FAKE_PROTOCOL = '__FAKE_PROTOCOL__'
FAKE_HOST = '__FAKE_HOST__'