This PR implements what we were talking about by email -- creating a folder containing metadata.db and .calnotes while in the GUI then passing that via the subprocess to the backend.

This commit is contained in:
Charles Haley 2025-03-09 13:17:46 +00:00
parent 2ec4e0b51c
commit 66d60438b9
4 changed files with 39 additions and 12 deletions

View File

@ -444,7 +444,7 @@ class DB:
def __init__(self, library_path, default_prefs=None, read_only=False, def __init__(self, library_path, default_prefs=None, read_only=False,
restore_all_prefs=False, progress_callback=lambda x, y:True, restore_all_prefs=False, progress_callback=lambda x, y:True,
load_user_formatter_functions=True): load_user_formatter_functions=True, temp_db_path=None):
self.is_closed = False self.is_closed = False
if isbytestring(library_path): if isbytestring(library_path):
library_path = library_path.decode(filesystem_encoding) library_path = library_path.decode(filesystem_encoding)
@ -452,8 +452,7 @@ class DB:
self.library_path = os.path.abspath(library_path) self.library_path = os.path.abspath(library_path)
self.dbpath = os.path.join(library_path, 'metadata.db') self.dbpath = os.path.join(library_path, 'metadata.db')
self.dbpath = os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', self.dbpath = os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', self.dbpath)
self.dbpath)
if iswindows and len(self.library_path) + 4*self.PATH_LIMIT + 10 > 259: if iswindows and len(self.library_path) + 4*self.PATH_LIMIT + 10 > 259:
raise ValueError(_( raise ValueError(_(
@ -468,7 +467,14 @@ class DB:
'Path to library too long. It must be less than' 'Path to library too long. It must be less than'
' %d characters.')%self.WINDOWS_LIBRARY_PATH_LIMIT) ' %d characters.')%self.WINDOWS_LIBRARY_PATH_LIMIT)
if read_only and os.path.exists(self.dbpath): if temp_db_path is not None:
if not os.path.exists(temp_db_path):
raise FileNotFoundError(f"temp_db_path '{temp_db_path} doesn't refer to a file")
# temp_db_path specifies a path to the database to use for this
# library. It should be in its own folder along with .calnotes.
# It overrides the environment variable CALIBRE_OVERRIDE_DATABASE_PATH.
self.dbpath = temp_db_path
elif read_only and os.path.exists(self.dbpath):
# Work on only a copy of metadata.db to ensure that # Work on only a copy of metadata.db to ensure that
# metadata.db is not changed # metadata.db is not changed
pt = PersistentTemporaryFile('_metadata_ro.db') pt = PersistentTemporaryFile('_metadata_ro.db')

View File

@ -42,11 +42,12 @@ def cleanup_tags(tags):
def create_backend( def create_backend(
library_path, default_prefs=None, read_only=False, library_path, default_prefs=None, read_only=False,
progress_callback=lambda x, y:True, restore_all_prefs=False, progress_callback=lambda x, y:True, restore_all_prefs=False,
load_user_formatter_functions=True): load_user_formatter_functions=True, temp_db_path=None):
return DB(library_path, default_prefs=default_prefs, return DB(library_path, default_prefs=default_prefs,
read_only=read_only, restore_all_prefs=restore_all_prefs, read_only=read_only, restore_all_prefs=restore_all_prefs,
progress_callback=progress_callback, progress_callback=progress_callback,
load_user_formatter_functions=load_user_formatter_functions) load_user_formatter_functions=load_user_formatter_functions,
temp_db_path=temp_db_path)
def set_global_state(db): def set_global_state(db):
@ -179,7 +180,8 @@ class LibraryDatabase:
def __init__(self, library_path, def __init__(self, library_path,
default_prefs=None, read_only=False, is_second_db=False, default_prefs=None, read_only=False, is_second_db=False,
progress_callback=None, restore_all_prefs=False, row_factory=False): progress_callback=None, restore_all_prefs=False, row_factory=False,
temp_db_path=None):
self.is_second_db = is_second_db self.is_second_db = is_second_db
if progress_callback is None: if progress_callback is None:
@ -190,7 +192,8 @@ class LibraryDatabase:
backend = self.backend = create_backend(library_path, default_prefs=default_prefs, backend = self.backend = create_backend(library_path, default_prefs=default_prefs,
read_only=read_only, restore_all_prefs=restore_all_prefs, read_only=read_only, restore_all_prefs=restore_all_prefs,
progress_callback=progress_callback, progress_callback=progress_callback,
load_user_formatter_functions=not is_second_db) load_user_formatter_functions=not is_second_db,
temp_db_path=temp_db_path)
cache = self.new_api = Cache(backend, library_database_instance=self) cache = self.new_api = Cache(backend, library_database_instance=self)
cache.init() cache.init()
self.data = View(cache) self.data = View(cache)

View File

@ -40,14 +40,16 @@ def gui_convert_override(input, output, recommendations, notification=DummyRepor
override_input_metadata=True) override_input_metadata=True)
def gui_catalog(fmt, title, dbspec, ids, out_file_name, sync, fmt_options, connected_device, 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: if log is None:
log = Log() log = Log()
from calibre.library import db
from calibre.utils.config import prefs from calibre.utils.config import prefs
prefs.refresh() prefs.refresh()
db = db(read_only=True)
# 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 db.catalog_plugin_on_device_temp_mapping = dbspec
# Create a minimal OptionParser that we can append to # Create a minimal OptionParser that we can append to

View File

@ -10,6 +10,7 @@ Logic for setting up conversion jobs
''' '''
import os import os
import shutil
from qt.core import QDialog, QProgressDialog, QTimer from qt.core import QDialog, QProgressDialog, QTimer
@ -27,7 +28,7 @@ from calibre.gui2.convert import bulk_defaults_for_input_format
from calibre.gui2.convert.bulk import BulkConfig from calibre.gui2.convert.bulk import BulkConfig
from calibre.gui2.convert.metadata import create_cover_file, create_opf_file from calibre.gui2.convert.metadata import create_cover_file, create_opf_file
from calibre.gui2.convert.single import Config as SingleConfig from calibre.gui2.convert.single import Config as SingleConfig
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryDirectory, PersistentTemporaryFile
from calibre.utils.config import prefs from calibre.utils.config import prefs
from polyglot.builtins import as_bytes from polyglot.builtins import as_bytes
@ -371,8 +372,23 @@ def generate_catalog(parent, dbspec, ids, device_manager, db): # {{{
except: except:
pass pass
# Create a temporary copy of the databases to pass into the generation
# 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'))
# These args are passed inline to gui2.convert.gui_conversion:gui_catalog # These args are passed inline to gui2.convert.gui_conversion:gui_catalog
args = [ args = [
library_path,
temp_db_path,
d.catalog_format, d.catalog_format,
d.catalog_title, d.catalog_title,
dbspec, dbspec,