mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Scan opened book/data folders for changes to extra files for a few minutes after they are opened, updating caches and the GUI as changes are detected
This commit is contained in:
parent
79cd85af35
commit
300c68b1e4
@ -272,24 +272,23 @@ class ViewAction(InterfaceAction):
|
|||||||
return
|
return
|
||||||
if not self._view_check(len(rows), max_=10, skip_dialog_name='open-folder-many-check'):
|
if not self._view_check(len(rows), max_=10, skip_dialog_name='open-folder-many-check'):
|
||||||
return
|
return
|
||||||
|
db = self.gui.current_db
|
||||||
for i, row in enumerate(rows):
|
for i, row in enumerate(rows):
|
||||||
db = self.gui.current_db
|
self.gui.extra_files_watcher.watch_book(db.id(row.row()))
|
||||||
db.new_api.clear_extra_files_cache(self.gui.library_view.model().id(row))
|
|
||||||
path = db.abspath(row.row())
|
path = db.abspath(row.row())
|
||||||
open_local_file(path)
|
if path:
|
||||||
if ismacos and i < len(rows) - 1:
|
open_local_file(path)
|
||||||
time.sleep(0.1) # Finder cannot handle multiple folder opens
|
if ismacos and i < len(rows) - 1:
|
||||||
|
time.sleep(0.1) # Finder cannot handle multiple folder opens
|
||||||
|
|
||||||
def view_folder_for_id(self, id_):
|
def view_folder_for_id(self, id_):
|
||||||
db = self.gui.current_db
|
self.gui.extra_files_watcher.watch_book(id_)
|
||||||
db.new_api.clear_extra_files_cache(id_)
|
path = self.gui.current_db.abspath(id_, index_is_id=True)
|
||||||
path = db.abspath(id_, index_is_id=True)
|
|
||||||
open_local_file(path)
|
open_local_file(path)
|
||||||
|
|
||||||
def view_data_folder_for_id(self, id_):
|
def view_data_folder_for_id(self, id_):
|
||||||
db = self.gui.current_db
|
self.gui.extra_files_watcher.watch_book(id_)
|
||||||
db.new_api.clear_extra_files_cache(id_)
|
path = self.gui.current_db.abspath(id_, index_is_id=True)
|
||||||
path = db.abspath(id_, index_is_id=True)
|
|
||||||
open_local_file(os.path.join(path, DATA_DIR_NAME))
|
open_local_file(os.path.join(path, DATA_DIR_NAME))
|
||||||
|
|
||||||
def view_book(self, triggered):
|
def view_book(self, triggered):
|
||||||
|
87
src/calibre/gui2/extra_files_watcher.py
Normal file
87
src/calibre/gui2/extra_files_watcher.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# License: GPLv3 Copyright: 2023, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
|
from qt.core import QObject, QTimer
|
||||||
|
from time import monotonic
|
||||||
|
from typing import NamedTuple, Tuple
|
||||||
|
|
||||||
|
from calibre.db.constants import DATA_FILE_PATTERN
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraFile(NamedTuple):
|
||||||
|
relpath: str
|
||||||
|
mtime: float
|
||||||
|
size: int
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraFiles(NamedTuple):
|
||||||
|
last_changed_at: float
|
||||||
|
files: Tuple[ExtraFile, ...]
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraFilesWatcher(QObject):
|
||||||
|
|
||||||
|
WATCH_FOR = 300 # seconds
|
||||||
|
TICK_INTERVAL = 1 # seconds
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.watched_book_ids = {}
|
||||||
|
self.timer = QTimer(self)
|
||||||
|
self.timer.setInterval(int(self.TICK_INTERVAL * 1000))
|
||||||
|
self.timer.timeout.connect(self.check_registered_books)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.watched_book_ids.clear()
|
||||||
|
self.timer.stop()
|
||||||
|
|
||||||
|
def watch_book(self, book_id):
|
||||||
|
if book_id not in self.watched_book_ids:
|
||||||
|
try:
|
||||||
|
self.watched_book_ids[book_id] = ExtraFiles(monotonic(), self.get_extra_files(book_id))
|
||||||
|
except Exception:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return
|
||||||
|
self.timer.start()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gui(self):
|
||||||
|
ans = self.parent()
|
||||||
|
if hasattr(ans, 'current_db'):
|
||||||
|
return ans
|
||||||
|
from calibre.gui2.ui import get_gui
|
||||||
|
return get_gui()
|
||||||
|
|
||||||
|
def get_extra_files(self, book_id):
|
||||||
|
db = self.gui.current_db.new_api
|
||||||
|
return tuple(ExtraFile(relpath, stat_result.st_mtime, stat_result.st_size) for
|
||||||
|
relpath, file_path, stat_result in db.list_extra_files(book_id, pattern=DATA_FILE_PATTERN))
|
||||||
|
|
||||||
|
def check_registered_books(self):
|
||||||
|
changed = {}
|
||||||
|
remove = set()
|
||||||
|
now = monotonic()
|
||||||
|
for book_id, extra_files in self.watched_book_ids.items():
|
||||||
|
try:
|
||||||
|
ef = self.get_extra_files(book_id)
|
||||||
|
except Exception:
|
||||||
|
# book probably deleted
|
||||||
|
remove.add(book_id)
|
||||||
|
continue
|
||||||
|
if ef != extra_files.files:
|
||||||
|
changed[book_id] = ef
|
||||||
|
elif now - extra_files.last_changed_at > self.WATCH_FOR:
|
||||||
|
remove.add(book_id)
|
||||||
|
if changed:
|
||||||
|
self.refresh_gui(changed)
|
||||||
|
for book_id, files in changed.items():
|
||||||
|
self.watched_book_ids[book_id] = self.watched_book_ids[book_id]._replace(files=files, last_changed_at=now)
|
||||||
|
for book_id in remove:
|
||||||
|
self.watched_book_ids.pop(book_id, None)
|
||||||
|
if not self.watched_book_ids:
|
||||||
|
self.timer.stop()
|
||||||
|
|
||||||
|
def refresh_gui(self, book_ids):
|
||||||
|
self.gui.library_view.model().refresh_ids(frozenset(book_ids), current_row=self.gui.library_view.currentIndex().row())
|
@ -30,6 +30,7 @@ from calibre.constants import (
|
|||||||
from calibre.customize import PluginInstallationType
|
from calibre.customize import PluginInstallationType
|
||||||
from calibre.customize.ui import available_store_plugins, interface_actions
|
from calibre.customize.ui import available_store_plugins, interface_actions
|
||||||
from calibre.db.legacy import LibraryDatabase
|
from calibre.db.legacy import LibraryDatabase
|
||||||
|
from calibre.gui2.extra_files_watcher import ExtraFilesWatcher
|
||||||
from calibre.gui2 import (
|
from calibre.gui2 import (
|
||||||
Dispatcher, GetMetadata, config, error_dialog, gprefs, info_dialog,
|
Dispatcher, GetMetadata, config, error_dialog, gprefs, info_dialog,
|
||||||
max_available_height, open_url, question_dialog, warning_dialog,
|
max_available_height, open_url, question_dialog, warning_dialog,
|
||||||
@ -118,6 +119,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
def __init__(self, opts, parent=None, gui_debug=None):
|
def __init__(self, opts, parent=None, gui_debug=None):
|
||||||
MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True)
|
MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True)
|
||||||
self.setWindowIcon(QApplication.instance().windowIcon())
|
self.setWindowIcon(QApplication.instance().windowIcon())
|
||||||
|
self.extra_files_watcher = ExtraFilesWatcher(self)
|
||||||
self.jobs_pointer = Pointer(self)
|
self.jobs_pointer = Pointer(self)
|
||||||
self.proceed_requested.connect(self.do_proceed,
|
self.proceed_requested.connect(self.do_proceed,
|
||||||
type=Qt.ConnectionType.QueuedConnection)
|
type=Qt.ConnectionType.QueuedConnection)
|
||||||
@ -914,6 +916,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.library_path = newloc
|
self.library_path = newloc
|
||||||
|
self.extra_files_watcher.clear()
|
||||||
prefs['library_path'] = self.library_path
|
prefs['library_path'] = self.library_path
|
||||||
self.book_on_device(None, reset=True)
|
self.book_on_device(None, reset=True)
|
||||||
db.set_book_on_device_func(self.book_on_device)
|
db.set_book_on_device_func(self.book_on_device)
|
||||||
@ -1148,6 +1151,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
self.shutdown_started.emit()
|
self.shutdown_started.emit()
|
||||||
self.show_shutdown_message()
|
self.show_shutdown_message()
|
||||||
self.server_change_notification_timer.stop()
|
self.server_change_notification_timer.stop()
|
||||||
|
self.extra_files_watcher.clear()
|
||||||
try:
|
try:
|
||||||
self.event_in_db.disconnect()
|
self.event_in_db.disconnect()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user