From e7f25cf847287fef82eb5cf8a32491b57eaa5c72 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 1 May 2023 13:25:05 +0100 Subject: [PATCH 1/3] Make model.refresh_rows() also clear the extra files cache for those books. --- src/calibre/gui2/library/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 9c4a51600a..c7beff58b2 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -405,6 +405,8 @@ class BooksModel(QAbstractTableModel): # {{{ def refresh_rows(self, rows, current_row=-1): self._clear_caches() cc = self.columnCount(QModelIndex()) - 1 + for r in rows: + self.db.new_api.clear_extra_files_cache(self.db.id(r)) for first_row, last_row in group_numbers(rows): self.dataChanged.emit(self.index(first_row, 0), self.index(last_row, cc)) if current_row >= 0 and first_row <= current_row <= last_row: From 7b1078ec2826bd0b2954fbeb7ae46403d2da83d7 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 1 May 2023 13:34:16 +0100 Subject: [PATCH 2/3] Add opening the data folder to the Open action. Allow it to have a shortcut. Create the data folder if it doesn't exist. --- src/calibre/gui2/actions/open.py | 11 +++++++++-- src/calibre/gui2/actions/view.py | 26 +++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/actions/open.py b/src/calibre/gui2/actions/open.py index 31c0b94c00..8998e5f343 100644 --- a/src/calibre/gui2/actions/open.py +++ b/src/calibre/gui2/actions/open.py @@ -5,6 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' +from functools import partial from calibre.gui2.actions import InterfaceAction @@ -12,13 +13,19 @@ from calibre.gui2.actions import InterfaceAction class OpenFolderAction(InterfaceAction): name = 'Open Folder' - action_spec = (_('Open containing folder'), 'document_open.png', + action_spec = (_('Open book folder'), 'document_open.png', _('Open the folder containing the current book\'s files'), _('O')) dont_add_to = frozenset(('context-menu-device',)) action_type = 'current' + action_add_menu = True + action_menu_clone_qaction = _('Open book folder') def genesis(self): - self.qaction.triggered.connect(self.gui.iactions['View'].view_folder) + va = self.gui.iactions['View'].view_folder + self.qaction.triggered.connect(va) + a = self.create_menu_action(self.qaction.menu(), 'show-data-folder', + _('Open book data folder'), icon='document_open.png', shortcut=tuple()) + a.triggered.connect(partial(va, data_folder=True)) def location_selected(self, loc): enabled = loc == 'library' diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py index db28358fb3..d7cf1d4b16 100644 --- a/src/calibre/gui2/actions/view.py +++ b/src/calibre/gui2/actions/view.py @@ -263,7 +263,7 @@ class ViewAction(InterfaceAction): 'cannot be stopped until complete. Do you wish to continue?' ) % num, show_copy_button=False, skip_dialog_name=skip_dialog_name) - def view_folder(self, *args): + def view_folder(self, *args, **kwargs): rows = self.gui.current_view().selectionModel().selectedRows() if not rows or len(rows) == 0: d = error_dialog(self.gui, _('Cannot open folder'), @@ -272,12 +272,25 @@ class ViewAction(InterfaceAction): return if not self._view_check(len(rows), max_=10, skip_dialog_name='open-folder-many-check'): return + data_folder = kwargs.get('data_folder', False) db = self.gui.current_db 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 data_folder: + path = os.path.join(path, DATA_DIR_NAME) + if not os.path.exists(path): + try: + os.mkdir(path) + except Exception as e: + error_dialog(self.gui, _('Failed to create folder'), str(e), show=True) + continue + try: + open_local_file(path) + except Exception as e: + # We shouldn't get here ... + error_dialog(self.gui, _('Cannot open folder'), str(e), show=True) if ismacos and i < len(rows) - 1: time.sleep(0.1) # Finder cannot handle multiple folder opens @@ -289,7 +302,14 @@ class ViewAction(InterfaceAction): def view_data_folder_for_id(self, id_): 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)) + path = os.path.join(path, DATA_DIR_NAME) + if not os.path.exists(path): + try: + os.mkdir(path) + except Exception as e: + error_dialog(self.gui, _('Failed to create folder'), str(e), show=True) + return + open_local_file(path) def view_book(self, triggered): rows = self.gui.current_view().selectionModel().selectedRows() From 65bc7643c7e6addcf43b59554593bc43448821f4 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 1 May 2023 19:12:47 +0100 Subject: [PATCH 3/3] Bug #2018227: User Categories: "Hide empty categories" failing for sub-categories --- src/calibre/gui2/tag_browser/model.py | 31 ++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index 732535cf27..de73096f13 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -816,13 +816,38 @@ class TagsModel(QAbstractItemModel): # {{{ process_one_node(category, collapse_model, self.db.new_api.fields['rating'].book_value_map, state_map.get(category.category_key, {})) - # Fix up the node tree, reordering as needed and deleting undisplayed nodes + # Fix up the node tree, reordering as needed and deleting undisplayed + # nodes. First, remove empty user category subnodes if needed. This is a + # recursive process because the hierarchical categories were combined + # together in process_one_node (above), which also computes the child + # count. + if self.prefs['tag_browser_hide_empty_categories']: + def process_uc_children(parent, depth): + new_children = [] + for node in parent.children: + if node.type == TagTreeItem.CATEGORY: + # I could De Morgan's this but I think it is more + # understandable this way + if node.category_key.startswith('@') and len(node.children) == 0: + pass + else: + new_children.append(node) + process_uc_children(node, depth+1) + else: + new_children.append(node) + parent.children = new_children + for node in self.root_item.children: + if node.category_key.startswith('@'): + process_uc_children(node, 1) + + # Now check the standard categories and root-level user categories, + # removing any hidden categories and if needed, empty categories new_children = [] for node in self.root_item.children: + if self.prefs['tag_browser_hide_empty_categories'] and len(node.child_tags()) == 0: + continue key = node.category_key if key in self.row_map: - if self.prefs['tag_browser_hide_empty_categories'] and len(node.child_tags()) == 0: - continue if self.hidden_categories: if key in self.hidden_categories: continue