From b445bd16bbd92f921bd8f8dac57dfd7da2735077 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Sat, 7 Nov 2026 11:52:32 +0800 Subject: [PATCH] fix(paths): normalize separators for organized and downloaded paths The Windows job has been red since the workflow auto-organize feature (#1177) landed: two assertions expected one separator form while the code produced another on Windows. - download_manager joined the posix relative path ("SDXL 1.0/utility") onto a native root, so save_dir came out mixed-separated. Normalize the joined directory with normpath. - auto-organize's flat-template branch returned the native root while the templated branch returned a posix path; ModelMoveService.move_model had the same split. Normalize both to the posix form the scanner stores. Linux-only runs cannot catch either because os.sep == "/". --- py/services/download_manager.py | 4 +++- py/services/model_file_service.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/py/services/download_manager.py b/py/services/download_manager.py index deb1b1b2..0421dbf5 100644 --- a/py/services/download_manager.py +++ b/py/services/download_manager.py @@ -2200,7 +2200,9 @@ class DownloadManager: return {"success": False, "error": "No save directory specified"} if relative_path: base_save_dir = save_dir - save_dir = os.path.join(save_dir, relative_path) + # relative_path is posix-normalized ("SDXL 1.0/utility"), so + # normalize the joined directory back to native separators. + save_dir = os.path.normpath(os.path.join(save_dir, relative_path)) # Security: validate path containment after joining resolved_dir = os.path.abspath(os.path.normpath(save_dir)) base_dir = os.path.abspath(os.path.normpath(base_save_dir)) diff --git a/py/services/model_file_service.py b/py/services/model_file_service.py index 89cc8aa3..dff1052f 100644 --- a/py/services/model_file_service.py +++ b/py/services/model_file_service.py @@ -494,8 +494,10 @@ class ModelFileService: # Check if already in root directory if os.path.normpath(current_dir) == os.path.normpath(current_root): return None # Signal to skip - - return current_root + + # Match the templated branch (and the scanner's stored paths): + # one separator form regardless of the configured layout. + return current_root.replace(os.sep, '/') else: # Calculate new relative path based on settings new_relative_path = calculate_relative_path_for_model(model, self.model_type) @@ -1153,7 +1155,7 @@ class ModelMoveService: from ..utils.utils import calculate_relative_path_for_model relative_path = calculate_relative_path_for_model(model_data, self.model_type) if relative_path: - target_path = os.path.join(target_path, relative_path).replace(os.sep, '/') + target_path = os.path.join(target_path, relative_path) elif not get_settings_manager().get_download_path_template(self.model_type): # Flat structure, target_path remains the root pass @@ -1161,6 +1163,9 @@ class ModelMoveService: # Could not calculate relative path (e.g. missing metadata) # Fallback to manual target_path or skip? pass + # Expose one form for both the flat and templated layouts; + # the scanner stores file paths posix-normalized. + target_path = target_path.replace(os.sep, '/') source_dir = os.path.dirname(file_path) if os.path.normpath(source_dir) == os.path.normpath(target_path):