Implement save/restore of last read position

This commit is contained in:
Kovid Goyal 2019-08-01 19:50:48 +05:30
parent 2eefa97a1d
commit 742f322f89
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 55 additions and 12 deletions

View File

@ -7,15 +7,22 @@ from __future__ import absolute_import, division, print_function, unicode_litera
import os import os
from threading import Thread from threading import Thread
from PyQt5.Qt import QDockWidget, Qt, pyqtSignal from PyQt5.Qt import QDockWidget, Qt, QTimer, pyqtSignal
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
from calibre.gui2.main_window import MainWindow from calibre.gui2.main_window import MainWindow
from calibre.gui2.viewer.convert_book import prepare_book from calibre.gui2.viewer.convert_book import prepare_book
from calibre.gui2.viewer.web_view import WebView, set_book_path from calibre.gui2.viewer.web_view import WebView, set_book_path
from calibre.utils.config import JSONConfig
from calibre.utils.ipc.simple_worker import WorkerError from calibre.utils.ipc.simple_worker import WorkerError
def viewer_data():
if not hasattr(viewer_data, 'ans'):
viewer_data.ans = JSONConfig('viewer-data')
return viewer_data.ans
class EbookViewer(MainWindow): class EbookViewer(MainWindow):
msg_from_anotherinstance = pyqtSignal(object) msg_from_anotherinstance = pyqtSignal(object)
@ -23,6 +30,9 @@ class EbookViewer(MainWindow):
def __init__(self): def __init__(self):
MainWindow.__init__(self, None) MainWindow.__init__(self, None)
self.current_book_data = {}
self.save_cfi_debounce_timer = t = QTimer(self)
t.setInterval(2000), t.timeout.connect(self.save_cfi)
self.book_prepared.connect(self.load_finished, type=Qt.QueuedConnection) self.book_prepared.connect(self.load_finished, type=Qt.QueuedConnection)
def create_dock(title, name, area, areas=Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea): def create_dock(title, name, area, areas=Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea):
@ -35,6 +45,7 @@ class EbookViewer(MainWindow):
self.toc_dock = create_dock(_('Table of Contents'), 'toc-dock', Qt.LeftDockWidgetArea) self.toc_dock = create_dock(_('Table of Contents'), 'toc-dock', Qt.LeftDockWidgetArea)
self.inspector_dock = create_dock(_('Inspector'), 'inspector', Qt.RightDockWidgetArea) self.inspector_dock = create_dock(_('Inspector'), 'inspector', Qt.RightDockWidgetArea)
self.web_view = WebView(self) self.web_view = WebView(self)
self.web_view.cfi_changed.connect(self.cfi_changed)
self.setCentralWidget(self.web_view) self.setCentralWidget(self.web_view)
def handle_commandline_arg(self, arg): def handle_commandline_arg(self, arg):
@ -51,6 +62,9 @@ class EbookViewer(MainWindow):
def load_ebook(self, pathtoebook, open_at=None): def load_ebook(self, pathtoebook, open_at=None):
# TODO: Implement open_at # TODO: Implement open_at
if self.save_cfi_debounce_timer.isActive():
self.save_cfi()
self.current_book_data = {}
t = Thread(name='LoadBook', target=self._load_ebook_worker, args=(pathtoebook, open_at)) t = Thread(name='LoadBook', target=self._load_ebook_worker, args=(pathtoebook, open_at))
t.daemon = True t.daemon = True
t.start() t.start()
@ -73,4 +87,29 @@ class EbookViewer(MainWindow):
det_msg=data['tb'], show=True) det_msg=data['tb'], show=True)
return return
set_book_path(data['base']) set_book_path(data['base'])
self.web_view.start_book_load() self.current_book_data = data
self.web_view.start_book_load(initial_cfi=self.initial_cfi_for_current_book())
def initial_cfi_for_current_book(self):
vd = viewer_data()
lrp = vd.get('last-read-positions', {})
return lrp.get('path', {}).get(self.current_book_data['pathtoebook'])
def cfi_changed(self, cfi):
if not self.current_book_data:
return
self.current_book_data['last_known_cfi'] = cfi
self.save_cfi_debounce_timer.start()
def save_cfi(self):
self.save_cfi_debounce_timer.stop()
vd = viewer_data()
lrp = vd.get('last-read-positions', {})
path = lrp.setdefault('path', {})
path[self.current_book_data['pathtoebook']] = self.current_book_data['last_known_cfi']
vd.set('last-read-positions', lrp)
def closeEvent(self, ev):
if self.save_cfi_debounce_timer.isActive():
self.save_cfi()
return MainWindow.closeEvent(self, ev)

View File

@ -7,7 +7,8 @@ from __future__ import absolute_import, division, print_function, unicode_litera
import os import os
from PyQt5.Qt import ( from PyQt5.Qt import (
QApplication, QBuffer, QByteArray, QHBoxLayout, QSize, QTimer, QUrl, QWidget QApplication, QBuffer, QByteArray, QHBoxLayout, QSize, QTimer, QUrl, QWidget,
pyqtSignal
) )
from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler
from PyQt5.QtWebEngineWidgets import ( from PyQt5.QtWebEngineWidgets import (
@ -238,6 +239,8 @@ class Inspector(QWidget):
class WebView(RestartingWebEngineView): class WebView(RestartingWebEngineView):
cfi_changed = pyqtSignal(object)
def __init__(self, parent=None): def __init__(self, parent=None):
self._host_widget = None self._host_widget = None
self.current_cfi = None self.current_cfi = None
@ -266,6 +269,7 @@ class WebView(RestartingWebEngineView):
cfi = frag[len('bookpos='):] cfi = frag[len('bookpos='):]
if cfi: if cfi:
self.current_cfi = cfi self.current_cfi = cfi
self.cfi_changed.emit(cfi)
@property @property
def host_widget(self): def host_widget(self):
@ -305,12 +309,12 @@ class WebView(RestartingWebEngineView):
for func, args in iteritems(self.pending_bridge_ready_actions): for func, args in iteritems(self.pending_bridge_ready_actions):
getattr(self.bridge, func)(*args) getattr(self.bridge, func)(*args)
def start_book_load(self): def start_book_load(self, initial_cfi=None):
key = (set_book_path.path,) key = (set_book_path.path,)
if self.bridge.ready: if self.bridge.ready:
self.bridge.start_book_load(key, vprefs['session_data']) self.bridge.start_book_load(key, vprefs['session_data'], initial_cfi)
else: else:
self.pending_bridge_ready_actions['start_book_load'] = key, vprefs['session_data'] self.pending_bridge_ready_actions['start_book_load'] = key, vprefs['session_data'], initial_cfi
def set_session_data(self, key, val): def set_session_data(self, key, val):
if key == '*' and val is None: if key == '*' and val is None:

View File

@ -367,7 +367,7 @@ class View:
cfi = '/' + rest cfi = '/' + rest
return name, cfi return name, cfi
def display_book(self, book): def display_book(self, book, initial_cfi):
self.hide_overlays() self.hide_overlays()
self.iframe.focus() self.iframe.focus()
is_current_book = self.book and self.book.key == book.key is_current_book = self.book and self.book.key == book.key
@ -383,7 +383,7 @@ class View:
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]
cfi = None cfi = initial_cfi or None
q = parse_url_params() q = parse_url_params()
if q.bookpos and q.bookpos.startswith('epubcfi(/'): if q.bookpos and q.bookpos.startswith('epubcfi(/'):
cfi = q.bookpos cfi = q.bookpos

View File

@ -126,7 +126,7 @@ def show_error(title, msg, details):
error_dialog(title, msg, details) error_dialog(title, msg, details)
def manifest_received(key, end_type, xhr, ev): def manifest_received(key, initial_cfi, end_type, xhr, ev):
nonlocal book nonlocal book
if end_type is 'load': if end_type is 'load':
book = new_book(key, {}) book = new_book(key, {})
@ -137,7 +137,7 @@ def manifest_received(key, end_type, xhr, ev):
book.is_complete = True book.is_complete = True
v'delete book.manifest["metadata"]' v'delete book.manifest["metadata"]'
v'delete book.manifest["last_read_positions"]' v'delete book.manifest["last_read_positions"]'
view.display_book(book) view.display_book(book, initial_cfi)
else: else:
error_dialog(_('Could not open book'), _( error_dialog(_('Could not open book'), _(
'Failed to load book manifest, click "Show details" for more info'), 'Failed to load book manifest, click "Show details" for more info'),
@ -177,12 +177,12 @@ def create_session_data(prefs):
@from_python @from_python
def start_book_load(key, prefs): def start_book_load(key, prefs, initial_cfi):
nonlocal view nonlocal view
if view is None: if view is None:
create_session_data(prefs) create_session_data(prefs)
view = View(document.getElementById('view')) view = View(document.getElementById('view'))
xhr = ajax('manifest', manifest_received.bind(None, key), ok_code=0) xhr = ajax('manifest', manifest_received.bind(None, key, initial_cfi), ok_code=0)
xhr.responseType = 'json' xhr.responseType = 'json'
xhr.send() xhr.send()