From 4915282ba1843e1125b19f4fe8edcce6706aba72 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 9 Mar 2025 19:49:55 +0530 Subject: [PATCH] Cleanup previous PR 1) Use the passed in db object 2) Delete the temp dir after generating the catalog 3) Use the correct source dir when copying 4) Move copying logic into backend 5) Hold the db read lock while copying --- src/calibre/db/backend.py | 9 ++++ src/calibre/db/cache.py | 4 ++ src/calibre/gui2/convert/gui_conversion.py | 55 ++++++++++++---------- src/calibre/gui2/tools.py | 13 ++--- 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index e7af550f0b..75fc8cb771 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1456,6 +1456,15 @@ class DB: self.user_version = 1 # }}} + def clone_for_readonly_access(self, dest_dir: str) -> str: + dbpath = os.path.abspath(self.conn.db_filename('main')) + clone_db_path = os.path.join(dest_dir, os.path.basename(dbpath)) + shutil.copy2(dbpath, clone_db_path) + notes_dir = os.path.join(os.path.dirname(dbpath), NOTES_DIR_NAME) + if os.path.exists(notes_dir): + shutil.copytree(notes_dir, os.path.join(dest_dir, NOTES_DIR_NAME)) + return clone_db_path + def normpath(self, path): path = os.path.abspath(os.path.realpath(path)) if not self.is_case_sensitive: diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 8bd77968b3..6a76c5e852 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -3513,6 +3513,10 @@ class Cache: dest_value.extend(src_value) self._set_field(field, {dest_id: dest_value}) + @read_api + def clone_for_readonly_access(self, dest_dir: str) -> str: + return self.backend.clone_for_readonly_access(dest_dir) + def import_library(library_key, importer, library_path, progress=None, abort=None): from calibre.db.backend import DB diff --git a/src/calibre/gui2/convert/gui_conversion.py b/src/calibre/gui2/convert/gui_conversion.py index 9791bd5873..ce21d3f4ee 100644 --- a/src/calibre/gui2/convert/gui_conversion.py +++ b/src/calibre/gui2/convert/gui_conversion.py @@ -41,7 +41,7 @@ def gui_convert_override(input, output, recommendations, notification=DummyRepor def gui_catalog(library_path, temp_db_path, fmt, title, dbspec, ids, out_file_name, sync, fmt_options, connected_device, - notification=DummyReporter(), log=None): + notification=DummyReporter(), log=None): if log is None: log = Log() from calibre.utils.config import prefs @@ -50,31 +50,36 @@ def gui_catalog(library_path, temp_db_path, fmt, title, dbspec, ids, out_file_na # Open the temp database created while still in the GUI thread from calibre.db.legacy import LibraryDatabase db = LibraryDatabase(library_path, temp_db_path=temp_db_path) - db.catalog_plugin_on_device_temp_mapping = dbspec + try: + db.catalog_plugin_on_device_temp_mapping = dbspec - # Create a minimal OptionParser that we can append to - parser = OptionParser() - args = [] - parser.add_option('--verbose', action='store_true', dest='verbose', default=True) - opts, args = parser.parse_args() + # Create a minimal OptionParser that we can append to + parser = OptionParser() + args = [] + parser.add_option('--verbose', action='store_true', dest='verbose', default=True) + opts, args = parser.parse_args() - # Populate opts - # opts.gui_search_text = something - opts.catalog_title = title - opts.connected_device = connected_device - opts.ids = ids - opts.search_text = None - opts.sort_by = None - opts.sync = sync + # Populate opts + # opts.gui_search_text = something + opts.catalog_title = title + opts.connected_device = connected_device + opts.ids = ids + opts.search_text = None + opts.sort_by = None + opts.sync = sync - # Extract the option dictionary to comma-separated lists - for option in fmt_options: - if isinstance(fmt_options[option],list): - setattr(opts, option, ','.join(fmt_options[option])) - else: - setattr(opts, option, fmt_options[option]) + # Extract the option dictionary to comma-separated lists + for option in fmt_options: + if isinstance(fmt_options[option],list): + setattr(opts, option, ','.join(fmt_options[option])) + else: + setattr(opts, option, fmt_options[option]) - # Fetch and run the plugin for fmt - # Returns 0 if successful, 1 if no catalog built - plugin = plugin_for_catalog_format(fmt) - return plugin.run(out_file_name, opts, db, notification=notification) + # Fetch and run the plugin for fmt + # Returns 0 if successful, 1 if no catalog built + plugin = plugin_for_catalog_format(fmt) + return plugin.run(out_file_name, opts, db, notification=notification) + finally: + import shutil + db.close() + shutil.rmtree(os.path.dirname(temp_db_path)) diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index 6f699e8076..ca6b2f31f9 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -10,7 +10,6 @@ Logic for setting up conversion jobs ''' import os -import shutil from qt.core import QDialog, QProgressDialog, QTimer @@ -376,18 +375,12 @@ def generate_catalog(parent, dbspec, ids, device_manager, db): # {{{ # process. The backend looks for the notes directory (.calnotes) in the # directory containing the metadata.db file. Copy the one from the current # library. - from calibre.gui2.ui import get_gui - library_path = get_gui().current_db.library_path - temp_db_directory = PersistentTemporaryDirectory('_callib') - temp_db_path = os.path.join(temp_db_directory, 'metadata.db') - shutil.copy(os.path.join(library_path, 'metadata.db'), temp_db_directory) - notes_dir = os.path.join(library_path, '.calnotes') - if os.path.exists(notes_dir): - shutil.copytree(notes_dir, os.path.join(temp_db_directory, '.calnotes')) + temp_db_directory = PersistentTemporaryDirectory('callib') + temp_db_path = db.new_api.clone_for_readonly_access(temp_db_directory) # These args are passed inline to gui2.convert.gui_conversion:gui_catalog args = [ - library_path, + db.library_path, temp_db_path, d.catalog_format, d.catalog_title,