mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement save/restore of last read position
This commit is contained in:
parent
2eefa97a1d
commit
742f322f89
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user