mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Wire up notification of changes for the embedded server
This commit is contained in:
parent
345842825b
commit
8535c6a1d8
@ -436,12 +436,13 @@ class AddAction(InterfaceAction):
|
||||
Adder(paths, db=None if to_device else self.gui.current_db,
|
||||
parent=self.gui, callback=partial(self._files_added, on_card=on_card), pool=self.gui.spare_pool())
|
||||
|
||||
def refresh_gui(self, num, set_current_row=-1):
|
||||
def refresh_gui(self, num, set_current_row=-1, recount=True):
|
||||
self.gui.library_view.model().books_added(num)
|
||||
if set_current_row > -1:
|
||||
self.gui.library_view.set_current_row(0)
|
||||
self.gui.refresh_cover_browser()
|
||||
self.gui.tags_view.recount()
|
||||
if recount:
|
||||
self.gui.tags_view.recount()
|
||||
|
||||
def _files_added(self, adder, on_card=None):
|
||||
if adder.items:
|
||||
|
59
src/calibre/gui2/changes.py
Normal file
59
src/calibre/gui2/changes.py
Normal file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from calibre.srv.changes import (
|
||||
BooksAdded, BooksDeleted, FormatsAdded, FormatsRemoved, MetadataChanged,
|
||||
SavedSearchesChanged
|
||||
)
|
||||
|
||||
|
||||
def handle_changes(changes, gui=None):
|
||||
if not changes:
|
||||
return
|
||||
if gui is None:
|
||||
from calibre.gui2.ui import get_gui
|
||||
gui = get_gui()
|
||||
if gui is None:
|
||||
return
|
||||
refresh_ids = set()
|
||||
added, removed = set(), set()
|
||||
ss_changed = False
|
||||
for change in changes:
|
||||
if isinstance(change, (FormatsAdded, FormatsRemoved, MetadataChanged)):
|
||||
refresh_ids |= change.book_ids
|
||||
elif isinstance(change, BooksAdded):
|
||||
added |= change.book_ids
|
||||
elif isinstance(change, BooksDeleted):
|
||||
removed |= change.book_ids
|
||||
elif isinstance(change, SavedSearchesChanged):
|
||||
ss_changed = True
|
||||
|
||||
if added and removed:
|
||||
gui.refresh_all()
|
||||
return
|
||||
refresh_ids -= added | removed
|
||||
orig = gui.tags_view.disable_recounting, gui.disable_cover_browser_refresh
|
||||
gui.tags_view.disable_recounting = gui.disable_cover_browser_refresh = True
|
||||
if added:
|
||||
gui.current_db.data.books_added(added)
|
||||
gui.iactions['Add Books'].refresh_gui(len(added), recount=False)
|
||||
if removed:
|
||||
next_id = gui.current_view().next_id
|
||||
m = gui.library_view.model()
|
||||
m.ids_deleted(removed)
|
||||
gui.iactions['Remove Books'].library_ids_deleted2(removed, next_id=next_id)
|
||||
# current = gui.library_view.currentIndex()
|
||||
# if current.isValid():
|
||||
# m.current_changed(current, current)
|
||||
# else:
|
||||
# gui.book_details.reset_info()
|
||||
if refresh_ids:
|
||||
gui.iactions['Edit Metadata'].refresh_books_after_metadata_edit(refresh_ids)
|
||||
if ss_changed:
|
||||
gui.saved_searches_changed(recount=False)
|
||||
gui.tags_view.disable_recounting = gui.disable_cover_browser_refresh = False
|
||||
gui.tags_view.recount(), gui.refresh_cover_browser()
|
||||
gui.tags_view.disable_recounting, gui.disable_cover_browser_refresh = orig
|
@ -292,6 +292,8 @@ class CBDialog(QDialog):
|
||||
|
||||
class CoverFlowMixin(object):
|
||||
|
||||
disable_cover_browser_refresh = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@ -405,6 +407,8 @@ class CoverFlowMixin(object):
|
||||
return not self.cb_splitter.is_side_index_hidden
|
||||
|
||||
def refresh_cover_browser(self):
|
||||
if self.disable_cover_browser_refresh:
|
||||
return
|
||||
try:
|
||||
if self.is_cover_browser_visible() and not isinstance(self.cover_flow, QLabel):
|
||||
self.db_images.ignore_image_requests = False
|
||||
@ -466,6 +470,7 @@ def test():
|
||||
def main(args=sys.argv):
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt5.Qt import QApplication, QMainWindow
|
||||
app = QApplication([])
|
||||
|
@ -385,6 +385,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
|
||||
def delete_books_by_id(self, ids, permanent=False):
|
||||
self.db.new_api.remove_books(ids, permanent=permanent)
|
||||
self.ids_deleted(ids)
|
||||
|
||||
def ids_deleted(self, ids):
|
||||
self.db.data.books_deleted(tuple(ids))
|
||||
self.db.notify('delete', list(ids))
|
||||
self.books_deleted()
|
||||
|
@ -475,7 +475,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
|
||||
def start_content_server(self, check_started=True):
|
||||
from calibre.srv.embedded import Server
|
||||
self.content_server = Server(self.library_broker, self.handle_changes_from_server)
|
||||
self.content_server = Server(self.library_broker, Dispatcher(self.handle_changes_from_server))
|
||||
self.content_server.state_callback = Dispatcher(
|
||||
self.iactions['Connect Share'].content_server_state_changed)
|
||||
if check_started:
|
||||
@ -484,8 +484,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
self.content_server.start()
|
||||
|
||||
def handle_changes_from_server(self, library_path, change_event):
|
||||
if DEBUG:
|
||||
prints('Received server change event: {} for {}'.format(change_event, library_path))
|
||||
if self.library_broker.is_gui_library(library_path):
|
||||
self.server_changes.push((library_path, change_event))
|
||||
self.server_changes.put((library_path, change_event))
|
||||
self.server_change_notification_timer.start()
|
||||
|
||||
def handle_changes_from_server_debounced(self):
|
||||
@ -500,7 +502,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
if self.library_broker.is_gui_library(library_path):
|
||||
changes.append(change_event)
|
||||
if changes:
|
||||
handle_changes(self, changes)
|
||||
handle_changes(changes, self)
|
||||
|
||||
def content_server_start_failed(self, msg):
|
||||
error_dialog(self, _('Failed to start Content server'),
|
||||
@ -587,6 +589,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
def refresh_all(self):
|
||||
m = self.library_view.model()
|
||||
m.db.data.refresh(clear_caches=False, do_search=False)
|
||||
self.saved_searches_changed(recount=False)
|
||||
m.resort()
|
||||
m.research()
|
||||
self.tags_view.recount()
|
||||
|
@ -1,4 +1,3 @@
|
||||
Wire up cdb notify_changes for the embeded server
|
||||
Prevent standalone and embedded servers from running simultaneously
|
||||
Prevent more than a single instance of the standalone server from running
|
||||
|
||||
|
@ -4,12 +4,14 @@
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from functools import partial
|
||||
|
||||
from calibre import as_unicode
|
||||
from calibre.db.cli import module_for_cmd
|
||||
from calibre.srv.errors import HTTPNotFound, HTTPBadRequest
|
||||
from calibre.srv.errors import HTTPBadRequest, HTTPNotFound
|
||||
from calibre.srv.routes import endpoint, msgpack_or_json
|
||||
from calibre.srv.utils import get_library_data
|
||||
from calibre.utils.serialize import MSGPACK_MIME, msgpack_loads, json_loads
|
||||
from calibre.utils.serialize import MSGPACK_MIME, json_loads, msgpack_loads
|
||||
|
||||
receive_data_methods = {'GET', 'POST'}
|
||||
|
||||
@ -39,7 +41,7 @@ def cdb_run(ctx, rd, which, version):
|
||||
if getattr(m, 'needs_srv_ctx', False):
|
||||
args = [ctx] + list(args)
|
||||
try:
|
||||
result = m.implementation(db, ctx.notify_changes, *args)
|
||||
result = m.implementation(db, partial(ctx.notify_changes, db.backend.library_path), *args)
|
||||
except Exception as err:
|
||||
import traceback
|
||||
return {'err': as_unicode(err), 'tb': traceback.format_exc()}
|
||||
|
@ -3,29 +3,80 @@
|
||||
# License: GPLv3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
from future_builtins import map
|
||||
|
||||
|
||||
def books_added(book_ids):
|
||||
pass
|
||||
class ChangeEvent(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(book_ids={})'.format(
|
||||
self.__class__.__name__, ','.join(sorted(map(str, self.book_ids)))
|
||||
)
|
||||
|
||||
|
||||
def formats_added(formats_map):
|
||||
# formats_map: {book_id:(fmt1, fmt2)}
|
||||
pass
|
||||
class BooksAdded(ChangeEvent):
|
||||
|
||||
def __init__(self, book_ids):
|
||||
ChangeEvent.__init__(self)
|
||||
self.book_ids = frozenset(book_ids)
|
||||
|
||||
|
||||
def formats_removed(formats_map):
|
||||
# formats_map: {book_id:(fmt1, fmt2)}
|
||||
pass
|
||||
class BooksDeleted(ChangeEvent):
|
||||
|
||||
def __init__(self, book_ids):
|
||||
ChangeEvent.__init__(self)
|
||||
self.book_ids = frozenset(book_ids)
|
||||
|
||||
|
||||
def books_deleted(book_ids):
|
||||
pass
|
||||
class FormatsAdded(ChangeEvent):
|
||||
|
||||
def __init__(self, formats_map):
|
||||
ChangeEvent.__init__(self)
|
||||
self.formats_map = formats_map
|
||||
|
||||
@property
|
||||
def book_ids(self):
|
||||
return frozenset(self.formats_map)
|
||||
|
||||
|
||||
def metadata(book_ids):
|
||||
pass
|
||||
class FormatsRemoved(ChangeEvent):
|
||||
|
||||
def __init__(self, formats_map):
|
||||
ChangeEvent.__init__(self)
|
||||
self.formats_map = formats_map
|
||||
|
||||
@property
|
||||
def book_ids(self):
|
||||
return frozenset(self.formats_map)
|
||||
|
||||
|
||||
def saved_searches(added=(), removed=()):
|
||||
pass
|
||||
class MetadataChanged(ChangeEvent):
|
||||
|
||||
def __init__(self, book_ids):
|
||||
ChangeEvent.__init__(self)
|
||||
self.book_ids = frozenset(book_ids)
|
||||
|
||||
|
||||
class SavedSearchesChanged(ChangeEvent):
|
||||
|
||||
def __init__(self, added=(), removed=()):
|
||||
ChangeEvent.__init__(self)
|
||||
self.added = frozenset(added)
|
||||
self.removed = frozenset(removed)
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(added={}, removed={})'.format(
|
||||
self.__class__.__name__,
|
||||
sorted(map(str, self.added)), sorted(map(str, self.removed))
|
||||
)
|
||||
|
||||
|
||||
books_added = BooksAdded
|
||||
formats_added = FormatsAdded
|
||||
formats_removed = FormatsRemoved
|
||||
books_deleted = BooksDeleted
|
||||
metadata = MetadataChanged
|
||||
saved_searches = SavedSearchesChanged
|
||||
|
@ -27,13 +27,13 @@ class Server(object):
|
||||
loop = current_thread = exception = None
|
||||
state_callback = start_failure_callback = None
|
||||
|
||||
def __init__(self, library_broker):
|
||||
def __init__(self, library_broker, notify_changes):
|
||||
opts = server_config()
|
||||
lp, lap = log_paths()
|
||||
log_size = opts.max_log_size * 1024 * 1024
|
||||
log = RotatingLog(lp, max_size=log_size)
|
||||
access_log = RotatingLog(lap, max_size=log_size)
|
||||
self.handler = Handler(library_broker, opts)
|
||||
self.handler = Handler(library_broker, opts, notify_changes=notify_changes)
|
||||
plugins = self.plugins = []
|
||||
if opts.use_bonjour:
|
||||
plugins.append(BonJour())
|
||||
|
@ -24,7 +24,7 @@ class Context(object):
|
||||
CATEGORY_CACHE_SIZE = 25
|
||||
SEARCH_CACHE_SIZE = 100
|
||||
|
||||
def __init__(self, libraries, opts, testing=False):
|
||||
def __init__(self, libraries, opts, testing=False, notify_changes=None):
|
||||
self.opts = opts
|
||||
self.library_broker = libraries if isinstance(libraries, LibraryBroker) else LibraryBroker(libraries)
|
||||
self.testing = testing
|
||||
@ -32,7 +32,11 @@ class Context(object):
|
||||
self.user_manager = UserManager(opts.userdb)
|
||||
self.ignored_fields = frozenset(filter(None, (x.strip() for x in (opts.ignored_fields or '').split(','))))
|
||||
self.displayed_fields = frozenset(filter(None, (x.strip() for x in (opts.displayed_fields or '').split(','))))
|
||||
self.notify_changes = lambda *a: None
|
||||
self._notify_changes = notify_changes
|
||||
|
||||
def notify_changes(self, library_path, change_event):
|
||||
if self._notify_changes is not None:
|
||||
self._notify_changes(library_path, change_event)
|
||||
|
||||
def start_job(self, name, module, func, args=(), kwargs=None, job_done_callback=None, job_data=None):
|
||||
return self.jobs_manager.start_job(name, module, func, args, kwargs, job_done_callback, job_data)
|
||||
@ -133,8 +137,8 @@ class Context(object):
|
||||
|
||||
class Handler(object):
|
||||
|
||||
def __init__(self, libraries, opts, testing=False):
|
||||
ctx = Context(libraries, opts, testing=testing)
|
||||
def __init__(self, libraries, opts, testing=False, notify_changes=None):
|
||||
ctx = Context(libraries, opts, testing=testing, notify_changes=notify_changes)
|
||||
self.auth_controller = None
|
||||
if opts.auth:
|
||||
has_ssl = opts.ssl_certfile is not None and opts.ssl_keyfile is not None
|
||||
|
@ -6,7 +6,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
||||
|
||||
import os
|
||||
from collections import OrderedDict, defaultdict
|
||||
from threading import Lock
|
||||
from threading import RLock as Lock
|
||||
|
||||
from calibre import filesystem_encoding
|
||||
from calibre.db.cache import Cache
|
||||
@ -189,6 +189,12 @@ class GuiLibraryBroker(LibraryBroker):
|
||||
olddb.close(), olddb.break_cycles()
|
||||
self._prune_loaded_dbs()
|
||||
|
||||
def is_gui_library(self, library_path):
|
||||
with self:
|
||||
if self.gui_library_id and self.gui_library_id in self.lmap:
|
||||
return samefile(library_path, self.lmap[self.gui_library_id])
|
||||
return False
|
||||
|
||||
def _prune_loaded_dbs(self):
|
||||
now = monotonic()
|
||||
for library_id in tuple(self.loaded_dbs):
|
||||
|
Loading…
x
Reference in New Issue
Block a user