mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
E-book viewer: When displaying estimated time to completion for reading a book, remember the reading rate the next time the book is opened so that the period spent calculating the time remaining is reduced. Fixes #1852929 [Time to read book is not saved](https://bugs.launchpad.net/calibre/+bug/1852929)
This commit is contained in:
parent
b2c8f6f8ee
commit
439da2712a
@ -1,9 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import json
|
||||
import os
|
||||
from calibre.constants import config_dir
|
||||
import tempfile
|
||||
|
||||
from calibre.constants import cache_dir, config_dir
|
||||
from calibre.utils.config import JSONConfig
|
||||
from calibre.utils.filenames import atomic_rename
|
||||
|
||||
vprefs = JSONConfig('viewer-webengine')
|
||||
viewer_config_dir = os.path.join(config_dir, 'viewer')
|
||||
@ -26,3 +30,43 @@ def get_session_pref(name, default=None, group='standalone_misc_settings'):
|
||||
def get_pref_group(name):
|
||||
sd = vprefs['session_data']
|
||||
return sd.get(name) or {}
|
||||
|
||||
|
||||
def reading_rates_path():
|
||||
return os.path.join(cache_dir(), 'viewer-reading-rates.json')
|
||||
|
||||
|
||||
def save_reading_rates(key, rates):
|
||||
path = reading_rates_path()
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
raw = f.read()
|
||||
except OSError:
|
||||
existing = {}
|
||||
else:
|
||||
existing = json.loads(raw)
|
||||
existing.pop(key, None)
|
||||
existing[key] = rates
|
||||
while len(existing) > 50:
|
||||
expired = next(iter(existing))
|
||||
del existing[expired]
|
||||
ddata = json.dumps(existing, indent=2).encode('utf-8')
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(dir=os.path.dirname(path), delete=False) as f:
|
||||
f.write(ddata)
|
||||
atomic_rename(f.name, path)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def load_reading_rates(key):
|
||||
path = reading_rates_path()
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
raw = f.read()
|
||||
except OSError:
|
||||
existing = {}
|
||||
else:
|
||||
existing = json.loads(raw)
|
||||
return existing.get(key)
|
||||
|
@ -30,7 +30,9 @@ from calibre.gui2.viewer.annotations import (
|
||||
AnnotationsSaveWorker, annotations_dir, parse_annotations
|
||||
)
|
||||
from calibre.gui2.viewer.bookmarks import BookmarkManager
|
||||
from calibre.gui2.viewer.config import get_session_pref, vprefs
|
||||
from calibre.gui2.viewer.config import (
|
||||
get_session_pref, load_reading_rates, save_reading_rates, vprefs
|
||||
)
|
||||
from calibre.gui2.viewer.convert_book import clean_running_workers, prepare_book
|
||||
from calibre.gui2.viewer.highlights import HighlightsPanel
|
||||
from calibre.gui2.viewer.integration import (
|
||||
@ -191,6 +193,7 @@ class EbookViewer(MainWindow):
|
||||
self.web_view.scrollbar_context_menu.connect(self.scrollbar_context_menu)
|
||||
self.web_view.close_prep_finished.connect(self.close_prep_finished)
|
||||
self.web_view.highlights_changed.connect(self.highlights_changed)
|
||||
self.web_view.update_reading_rates.connect(self.update_reading_rates)
|
||||
self.web_view.edit_book.connect(self.edit_book)
|
||||
self.actions_toolbar.initialize(self.web_view, self.search_dock.toggleViewAction())
|
||||
at.update_action_state(False)
|
||||
@ -487,6 +490,7 @@ class EbookViewer(MainWindow):
|
||||
self.setWindowTitle(_('Loading book') + f'… — {self.base_window_title}')
|
||||
self.loading_overlay(_('Loading book, please wait'))
|
||||
self.save_annotations()
|
||||
self.save_reading_rates()
|
||||
self.current_book_data = {}
|
||||
get_current_book_data(self.current_book_data)
|
||||
self.search_widget.clear_searches()
|
||||
@ -577,7 +581,8 @@ class EbookViewer(MainWindow):
|
||||
initial_position = {'type': 'bookpos', 'data': float(open_at)}
|
||||
highlights = self.current_book_data['annotations_map']['highlight']
|
||||
self.highlights_widget.load(highlights)
|
||||
self.web_view.start_book_load(initial_position=initial_position, highlights=highlights, current_book_data=self.current_book_data)
|
||||
rates = load_reading_rates(self.current_book_data['annotations_path_key'])
|
||||
self.web_view.start_book_load(initial_position=initial_position, highlights=highlights, current_book_data=self.current_book_data, reading_rates=rates)
|
||||
performance_monitor('webview loading requested')
|
||||
|
||||
def load_book_data(self, calibre_book_data=None):
|
||||
@ -666,6 +671,20 @@ class EbookViewer(MainWindow):
|
||||
get_session_pref('sync_annots_user', default='')
|
||||
)
|
||||
|
||||
def update_reading_rates(self, rates):
|
||||
if not self.current_book_data:
|
||||
return
|
||||
self.current_book_data['reading_rates'] = rates
|
||||
self.save_reading_rates()
|
||||
|
||||
def save_reading_rates(self):
|
||||
if not self.current_book_data:
|
||||
return
|
||||
key = self.current_book_data.get('annotations_path_key')
|
||||
rates = self.current_book_data.get('reading_rates')
|
||||
if key and rates:
|
||||
save_reading_rates(key, rates)
|
||||
|
||||
def highlights_changed(self, highlights):
|
||||
if not self.current_book_data:
|
||||
return
|
||||
@ -763,6 +782,7 @@ class EbookViewer(MainWindow):
|
||||
try:
|
||||
self.save_state()
|
||||
self.save_annotations()
|
||||
self.save_reading_rates()
|
||||
if self.annotations_saver is not None:
|
||||
self.annotations_saver.shutdown()
|
||||
self.annotations_saver = None
|
||||
|
@ -274,6 +274,7 @@ class ViewerBridge(Bridge):
|
||||
edit_book = from_js(object, object, object)
|
||||
show_book_folder = from_js()
|
||||
show_help = from_js(object)
|
||||
update_reading_rates = from_js(object)
|
||||
|
||||
create_view = to_js()
|
||||
start_book_load = to_js()
|
||||
@ -472,6 +473,7 @@ class WebView(RestartingWebEngineView):
|
||||
scrollbar_context_menu = pyqtSignal(object, object, object)
|
||||
close_prep_finished = pyqtSignal(object)
|
||||
highlights_changed = pyqtSignal(object)
|
||||
update_reading_rates = pyqtSignal(object)
|
||||
edit_book = pyqtSignal(object, object, object)
|
||||
shortcuts_changed = pyqtSignal(object)
|
||||
paged_mode_changed = pyqtSignal()
|
||||
@ -534,6 +536,7 @@ class WebView(RestartingWebEngineView):
|
||||
self.bridge.scrollbar_context_menu.connect(self.scrollbar_context_menu)
|
||||
self.bridge.close_prep_finished.connect(self.close_prep_finished)
|
||||
self.bridge.highlights_changed.connect(self.highlights_changed)
|
||||
self.bridge.update_reading_rates.connect(self.update_reading_rates)
|
||||
self.bridge.edit_book.connect(self.edit_book)
|
||||
self.bridge.show_book_folder.connect(self.show_book_folder)
|
||||
self.bridge.show_help.connect(self.show_help)
|
||||
@ -638,10 +641,10 @@ class WebView(RestartingWebEngineView):
|
||||
def on_content_file_changed(self, data):
|
||||
self.current_content_file = data
|
||||
|
||||
def start_book_load(self, initial_position=None, highlights=None, current_book_data=None):
|
||||
def start_book_load(self, initial_position=None, highlights=None, current_book_data=None, reading_rates=None):
|
||||
key = (set_book_path.path,)
|
||||
book_url = link_prefix_for_location_links(add_open_at=False)
|
||||
self.execute_when_ready('start_book_load', key, initial_position, set_book_path.pathtoebook, highlights or [], book_url)
|
||||
self.execute_when_ready('start_book_load', key, initial_position, set_book_path.pathtoebook, highlights or [], book_url, reading_rates)
|
||||
|
||||
def execute_when_ready(self, action, *args):
|
||||
if self.bridge.ready:
|
||||
|
@ -2,6 +2,8 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
from __python__ import bound_methods, hash_literals
|
||||
|
||||
from read_book.globals import ui_operations
|
||||
|
||||
THRESHOLD = 5
|
||||
FILTER_THRESHOLD = 25
|
||||
MAX_SAMPLES = 256
|
||||
@ -16,6 +18,8 @@ class Timers:
|
||||
def start_book(self, book):
|
||||
self.reset_read_timer()
|
||||
self.rates = v'[]'
|
||||
if book.saved_reading_rates?.rates:
|
||||
self.rates = book.saved_reading_rates.rates.slice(0)
|
||||
|
||||
def reset_read_timer(self):
|
||||
self.last_scroll_at = None
|
||||
@ -54,6 +58,8 @@ class Timers:
|
||||
self.rates.shift()
|
||||
self.rates.push(rate)
|
||||
self.calculate()
|
||||
if ui_operations.update_reading_rates:
|
||||
ui_operations.update_reading_rates({'rates': self.rates.slice(0)})
|
||||
|
||||
def time_for(self, length):
|
||||
if length >= 0 and self.rates.length >= THRESHOLD and self.average > 0:
|
||||
|
@ -95,7 +95,7 @@ def show_error(title, msg, details):
|
||||
to_python.show_error(title, msg, details)
|
||||
|
||||
|
||||
def manifest_received(key, initial_position, pathtoebook, highlights, book_url, end_type, xhr, ev):
|
||||
def manifest_received(key, initial_position, pathtoebook, highlights, book_url, reading_rates, end_type, xhr, ev):
|
||||
nonlocal book
|
||||
end_type = workaround_qt_bug(xhr, end_type)
|
||||
if end_type is 'load':
|
||||
@ -107,6 +107,7 @@ def manifest_received(key, initial_position, pathtoebook, highlights, book_url,
|
||||
book.highlights = highlights
|
||||
book.stored_files = {}
|
||||
book.calibre_book_url = book_url
|
||||
book.saved_reading_rates = reading_rates
|
||||
book.is_complete = True
|
||||
v'delete book.manifest["metadata"]'
|
||||
v'delete book.manifest["last_read_positions"]'
|
||||
@ -228,8 +229,8 @@ def show_home_page():
|
||||
|
||||
|
||||
@from_python
|
||||
def start_book_load(key, initial_position, pathtoebook, highlights, book_url):
|
||||
xhr = ajax('manifest', manifest_received.bind(None, key, initial_position, pathtoebook, highlights, book_url), ok_code=0)
|
||||
def start_book_load(key, initial_position, pathtoebook, highlights, book_url, reading_rates):
|
||||
xhr = ajax('manifest', manifest_received.bind(None, key, initial_position, pathtoebook, highlights, book_url, reading_rates), ok_code=0)
|
||||
xhr.responseType = 'json'
|
||||
xhr.send()
|
||||
|
||||
@ -439,6 +440,8 @@ if window is window.top:
|
||||
to_python.show_help(which)
|
||||
ui_operations.on_iframe_ready = def():
|
||||
to_python.on_iframe_ready()
|
||||
ui_operations.update_reading_rates = def(rates):
|
||||
to_python.update_reading_rates(rates)
|
||||
|
||||
document.body.appendChild(E.div(id='view'))
|
||||
window.onerror = onerror
|
||||
|
Loading…
x
Reference in New Issue
Block a user