This commit is contained in:
Wenaka 2026-07-06 17:34:00 +08:00 committed by GitHub
commit 1ad787345b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,4 +1,5 @@
import os import os
import sys
import time import time
import mimetypes import mimetypes
import logging import logging
@ -7,6 +8,34 @@ from collections.abc import Collection
from comfy.cli_args import args from comfy.cli_args import args
# exFAT does not update directory mtime when files are added/removed,
# so the cache invalidation needs an extra check on such filesystems.
_exfat_checked = False
_is_exfat_cache: dict[str, bool] = {}
def _is_exfat(path: str) -> bool:
"""Check if the given path resides on an exFAT volume (Windows only)."""
global _exfat_checked, _is_exfat_cache
if sys.platform != "win32":
return False
# Cache per drive letter
drive = os.path.splitdrive(path)[0]
if drive in _is_exfat_cache:
return _is_exfat_cache[drive]
try:
import ctypes
kernel32 = ctypes.windll.kernel32
# Get volume information
fs_name = ctypes.create_unicode_buffer(256)
if kernel32.GetVolumeInformationW(drive + "\\", None, 0, None, None, None, fs_name, 256):
result = fs_name.value.upper() == "EXFAT"
else:
result = False
except Exception:
result = False
_is_exfat_cache[drive] = result
return result
supported_pt_extensions: set[str] = {'.ckpt', '.pt', '.pt2', '.bin', '.pth', '.safetensors', '.pkl', '.sft'} supported_pt_extensions: set[str] = {'.ckpt', '.pt', '.pt2', '.bin', '.pth', '.safetensors', '.pkl', '.sft'}
folder_names_and_paths: dict[str, tuple[list[str], set[str]]] = {} folder_names_and_paths: dict[str, tuple[list[str], set[str]]] = {}
@ -369,19 +398,22 @@ def get_folder_paths(folder_name: str) -> list[str]:
folder_name = map_legacy(folder_name) folder_name = map_legacy(folder_name)
return folder_names_and_paths[folder_name][0][:] return folder_names_and_paths[folder_name][0][:]
def recursive_search(directory: str, excluded_dir_names: list[str] | None=None) -> tuple[list[str], dict[str, float]]: def recursive_search(directory: str, excluded_dir_names: list[str] | None=None) -> tuple[list[str], dict[str, float], dict[str, int]]:
if not os.path.isdir(directory): if not os.path.isdir(directory):
return [], {} return [], {}, {}
if excluded_dir_names is None: if excluded_dir_names is None:
excluded_dir_names = [] excluded_dir_names = []
result = [] result = []
dirs = {} dirs = {}
entry_counts = {}
# Attempt to add the initial directory to dirs with error handling # Attempt to add the initial directory to dirs with error handling
try: try:
dirs[directory] = os.path.getmtime(directory) dirs[directory] = os.path.getmtime(directory)
if _is_exfat(directory):
entry_counts[directory] = len(os.listdir(directory))
except FileNotFoundError: except FileNotFoundError:
logging.warning(f"Warning: Unable to access {directory}. Skipping this path.") logging.warning(f"Warning: Unable to access {directory}. Skipping this path.")
@ -404,11 +436,13 @@ def recursive_search(directory: str, excluded_dir_names: list[str] | None=None)
path: str = os.path.join(dirpath, d) path: str = os.path.join(dirpath, d)
try: try:
dirs[path] = os.path.getmtime(path) dirs[path] = os.path.getmtime(path)
if _is_exfat(path):
entry_counts[path] = len(os.listdir(path))
except FileNotFoundError: except FileNotFoundError:
logging.warning(f"Warning: Unable to access {path}. Skipping this path.") logging.warning(f"Warning: Unable to access {path}. Skipping this path.")
continue continue
logging.debug("found {} files".format(len(result))) logging.debug("found {} files".format(len(result)))
return result, dirs return result, dirs, entry_counts
def filter_files_extensions(files: Collection[str], extensions: Collection[str]) -> list[str]: def filter_files_extensions(files: Collection[str], extensions: Collection[str]) -> list[str]:
return sorted(list(filter(lambda a: os.path.splitext(a)[-1].lower() in extensions or len(extensions) == 0, files))) return sorted(list(filter(lambda a: os.path.splitext(a)[-1].lower() in extensions or len(extensions) == 0, files)))
@ -451,11 +485,16 @@ def get_filename_list_(folder_name: str) -> tuple[list[str], dict[str, float], f
output_list = set() output_list = set()
folders = folder_names_and_paths[folder_name] folders = folder_names_and_paths[folder_name]
output_folders = {} output_folders = {}
all_entry_counts = {}
for x in folders[0]: for x in folders[0]:
files, folders_all = recursive_search(x, excluded_dir_names=[".git"]) files, folders_all, entry_counts = recursive_search(x, excluded_dir_names=[".git"])
output_list.update(filter_files_extensions(files, folders[1])) output_list.update(filter_files_extensions(files, folders[1]))
output_folders = {**output_folders, **folders_all} output_folders = {**output_folders, **folders_all}
# Store entry counts with a key suffix to avoid colliding with mtime keys
for k, v in entry_counts.items():
all_entry_counts[k + "::entry_count"] = v
output_folders.update(all_entry_counts)
return sorted(list(output_list)), output_folders, time.perf_counter() return sorted(list(output_list)), output_folders, time.perf_counter()
def cached_filename_list_(folder_name: str) -> tuple[list[str], dict[str, float], float] | None: def cached_filename_list_(folder_name: str) -> tuple[list[str], dict[str, float], float] | None:
@ -471,10 +510,25 @@ def cached_filename_list_(folder_name: str) -> tuple[list[str], dict[str, float]
out = filename_list_cache[folder_name] out = filename_list_cache[folder_name]
for x in out[1]: for x in out[1]:
# Skip entry count keys used for exFAT cache invalidation
if x.endswith("::entry_count"):
continue
time_modified = out[1][x] time_modified = out[1][x]
folder = x folder = x
if os.path.getmtime(folder) != time_modified: try:
current_mtime = os.path.getmtime(folder)
except FileNotFoundError:
return None return None
if current_mtime != time_modified:
return None
# exFAT does not update directory mtime on file changes;
# use entry count as a fallback invalidation signal.
if _is_exfat(folder):
try:
if len(os.listdir(folder)) != out[1][folder + "::entry_count"]:
return None
except (KeyError, FileNotFoundError):
return None
folders = folder_names_and_paths[folder_name] folders = folder_names_and_paths[folder_name]
for x in folders[0]: for x in folders[0]: