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
|
||||
if not self._view_check(len(rows), max_=10, skip_dialog_name='open-folder-many-check'):
|
||||
return
|
||||
for i, row in enumerate(rows):
|
||||
db = self.gui.current_db
|
||||
db.new_api.clear_extra_files_cache(self.gui.library_view.model().id(row))
|
||||
for i, row in enumerate(rows):
|
||||
self.gui.extra_files_watcher.watch_book(db.id(row.row()))
|
||||
path = db.abspath(row.row())
|
||||
if path:
|
||||
open_local_file(path)
|
||||
if ismacos and i < len(rows) - 1:
|
||||
time.sleep(0.1) # Finder cannot handle multiple folder opens
|
||||
|
||||
def view_folder_for_id(self, id_):
|
||||
db = self.gui.current_db
|
||||
db.new_api.clear_extra_files_cache(id_)
|
||||
path = db.abspath(id_, index_is_id=True)
|
||||
self.gui.extra_files_watcher.watch_book(id_)
|
||||
path = self.gui.current_db.abspath(id_, index_is_id=True)
|
||||
open_local_file(path)
|
||||
|
||||
def view_data_folder_for_id(self, id_):
|
||||
db = self.gui.current_db
|
||||
db.new_api.clear_extra_files_cache(id_)
|
||||
path = db.abspath(id_, index_is_id=True)
|
||||
self.gui.extra_files_watcher.watch_book(id_)
|
||||
path = self.gui.current_db.abspath(id_, index_is_id=True)
|
||||
open_local_file(os.path.join(path, DATA_DIR_NAME))
|
||||
|
||||
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.ui import available_store_plugins, interface_actions
|
||||
from calibre.db.legacy import LibraryDatabase
|
||||
from calibre.gui2.extra_files_watcher import ExtraFilesWatcher
|
||||
from calibre.gui2 import (
|
||||
Dispatcher, GetMetadata, config, error_dialog, gprefs, info_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):
|
||||
MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True)
|
||||
self.setWindowIcon(QApplication.instance().windowIcon())
|
||||
self.extra_files_watcher = ExtraFilesWatcher(self)
|
||||
self.jobs_pointer = Pointer(self)
|
||||
self.proceed_requested.connect(self.do_proceed,
|
||||
type=Qt.ConnectionType.QueuedConnection)
|
||||
@ -914,6 +916,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.library_path = newloc
|
||||
self.extra_files_watcher.clear()
|
||||
prefs['library_path'] = self.library_path
|
||||
self.book_on_device(None, reset=True)
|
||||
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.show_shutdown_message()
|
||||
self.server_change_notification_timer.stop()
|
||||
self.extra_files_watcher.clear()
|
||||
try:
|
||||
self.event_in_db.disconnect()
|
||||
except Exception:
|
||||
|
Loading…
x
Reference in New Issue
Block a user