diff --git a/py/routes/update_routes.py b/py/routes/update_routes.py index 7628f334..95038838 100644 --- a/py/routes/update_routes.py +++ b/py/routes/update_routes.py @@ -11,6 +11,7 @@ from typing import Dict, List from ..utils.settings_paths import ensure_settings_file from ..services.downloader import get_downloader +from ..services.service_registry import ServiceRegistry logger = logging.getLogger(__name__) @@ -212,8 +213,19 @@ class UpdateRoutes: zip_path = tmp_zip_path - # Skip both settings.json, civitai and model cache folder - UpdateRoutes._clean_plugin_folder(plugin_root, skip_files=['settings.json', 'civitai', 'model_cache']) + # Close the downloaded-versions SQLite connection before cleaning, + # so that shutil.rmtree() does not fail on Windows (the process + # cannot delete a file with an outstanding open handle). + try: + history_svc = ServiceRegistry._services.get("downloaded_version_history_service") + if history_svc is not None: + history_svc.close() + logger.info("Closed downloaded-version history database connection") + except Exception: + logger.debug("Could not close downloaded-version history database", exc_info=True) + + # Skip settings.json, civitai, model cache and runtime cache folders + UpdateRoutes._clean_plugin_folder(plugin_root, skip_files=['settings.json', 'civitai', 'model_cache', 'cache']) # Extract ZIP to temp dir with tempfile.TemporaryDirectory() as tmp_dir: diff --git a/py/services/downloaded_version_history_service.py b/py/services/downloaded_version_history_service.py index 00d0bc7d..083d8fb9 100644 --- a/py/services/downloaded_version_history_service.py +++ b/py/services/downloaded_version_history_service.py @@ -96,6 +96,21 @@ class DownloadedVersionHistoryService: def get_database_path(self) -> str: return self._db_path + def close(self) -> None: + """Close the persistent SQLite connection, if open. + + This is called before plugin update operations to release the + database file lock on Windows, allowing ``shutil.rmtree()`` to + succeed when the cache resides inside the plugin directory. + """ + if self._conn is not None: + try: + self._conn.close() + except Exception: + pass + finally: + self._conn = None + def _get_active_library_name(self) -> str | None: try: value = self._settings.get_active_library_name()