Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions app/assets/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ class _AssetAccumulator(TypedDict):
refs: list[_RefInfo]


# Temp is deliberately absent: it is wiped before every scan, so walking it finds nothing.
RootType = Literal["models", "input", "output"]


def get_prefixes_for_root(root: RootType) -> list[str]:
def get_scan_prefixes_for_root(root: RootType) -> list[str]:
if root == "models":
bases: list[str] = []
for _bucket, paths, _exts in get_comfy_models_folders():
Expand All @@ -73,10 +74,15 @@ def get_prefixes_for_root(root: RootType) -> list[str]:
return []


def get_all_known_prefixes() -> list[str]:
"""Get all known asset prefixes across all root types."""
all_roots: tuple[RootType, ...] = ("models", "input", "output")
return [p for root in all_roots for p in get_prefixes_for_root(root)]
def get_owned_prefixes() -> list[str]:
"""Every directory an asset may live in; references outside these are marked missing."""
scan_roots: tuple[RootType, ...] = ("models", "input", "output")
prefixes = [p for root in scan_roots for p in get_scan_prefixes_for_root(root)]
return prefixes + get_temp_prefixes()


def get_temp_prefixes() -> list[str]:
return [os.path.abspath(folder_paths.get_temp_directory())]


def collect_models_files() -> list[str]:
Expand Down Expand Up @@ -107,7 +113,21 @@ def sync_references_with_filesystem(
collect_existing_paths: bool = False,
update_missing_tags: bool = False,
) -> set[str] | None:
"""Reconcile asset references with filesystem for a root.
return sync_prefixes_with_filesystem(
session,
get_scan_prefixes_for_root(root),
collect_existing_paths=collect_existing_paths,
update_missing_tags=update_missing_tags,
)


def sync_prefixes_with_filesystem(
session,
prefixes: list[str],
collect_existing_paths: bool = False,
update_missing_tags: bool = False,
) -> set[str] | None:
"""Reconcile asset references with filesystem under the given prefixes.

- Toggle needs_verify per reference using mtime/size stat check
- For hashed assets with at least one stat-unchanged ref: delete stale missing refs
Expand All @@ -117,14 +137,13 @@ def sync_references_with_filesystem(

Args:
session: Database session
root: Root type to scan
prefixes: Absolute directory prefixes whose references to reconcile
collect_existing_paths: If True, return set of surviving file paths
update_missing_tags: If True, update 'missing' tags based on file status

Returns:
Set of surviving absolute paths if collect_existing_paths=True, else None
"""
prefixes = get_prefixes_for_root(root)
if not prefixes:
return set() if collect_existing_paths else None

Expand Down Expand Up @@ -251,6 +270,16 @@ def sync_root_safely(root: RootType) -> set[str]:
return set()


def sync_temp_references_safely() -> None:
"""Retire temp references whose file is gone; temp is never scanned, so nothing else stats them."""
try:
with create_session() as sess:
sync_prefixes_with_filesystem(sess, get_temp_prefixes())
sess.commit()
except Exception as e:
logging.exception("temp reference sync failed: %s", e)


def mark_missing_outside_prefixes_safely(prefixes: list[str]) -> int:
"""Mark references as missing when outside the given prefixes.

Expand Down Expand Up @@ -384,7 +413,7 @@ def get_unenriched_assets_for_roots(
"""
prefixes: list[str] = []
for root in roots:
prefixes.extend(get_prefixes_for_root(root))
prefixes.extend(get_scan_prefixes_for_root(root))

if not prefixes:
return []
Expand Down
12 changes: 7 additions & 5 deletions app/assets/seeder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
build_asset_specs,
collect_paths_for_roots,
enrich_assets_batch,
get_all_known_prefixes,
get_prefixes_for_root,
get_owned_prefixes,
get_scan_prefixes_for_root,
get_unenriched_assets_for_roots,
insert_asset_specs,
mark_missing_outside_prefixes_safely,
sync_root_safely,
sync_temp_references_safely,
)
from app.database.db import dependencies_available

Expand Down Expand Up @@ -413,7 +414,7 @@ def mark_missing_outside_prefixes(self) -> int:
)
return 0

all_prefixes = get_all_known_prefixes()
all_prefixes = get_owned_prefixes()
marked = mark_missing_outside_prefixes_safely(all_prefixes)
if marked > 0:
logging.info("Marked %d references as missing", marked)
Expand Down Expand Up @@ -523,7 +524,7 @@ def _log_scan_config(self, roots: tuple[RootType, ...]) -> None:
os.path.abspath(folder_paths.models_dir),
)
else:
prefixes = get_prefixes_for_root(root)
prefixes = get_scan_prefixes_for_root(root)
if prefixes:
logging.info("Asset scan [%s] directories: %s", root, prefixes)

Expand All @@ -548,10 +549,11 @@ def _run_scan(self) -> None:
return

if self._prune_first:
all_prefixes = get_all_known_prefixes()
all_prefixes = get_owned_prefixes()
marked = mark_missing_outside_prefixes_safely(all_prefixes)
if marked > 0:
logging.info("Marked %d refs as missing before scan", marked)
sync_temp_references_safely()

if self._check_pause_and_cancel():
logging.info("Asset scan cancelled after pruning phase")
Expand Down
28 changes: 14 additions & 14 deletions tests-unit/assets_test/test_sync_references.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def test_needs_verify_toggling(session, temp_dir, case):
)
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
sync_references_with_filesystem(session, "models")
session.commit()

Expand Down Expand Up @@ -185,7 +185,7 @@ def test_is_missing_flag(session, temp_dir, case):
_make_asset(session, "a1", fp, "r1", asset_hash="blake3:abc", mtime_ns=mtime)
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
sync_references_with_filesystem(session, "models")
session.commit()

Expand All @@ -200,7 +200,7 @@ def test_seed_asset_all_missing_deletes_asset(session, temp_dir):
_make_asset(session, "seed1", fp, "r1", asset_hash=None, mtime_ns=999)
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
sync_references_with_filesystem(session, "models")
session.commit()

Expand All @@ -215,7 +215,7 @@ def test_seed_asset_some_exist_returns_survivors(session, temp_dir):
_make_asset(session, "seed1", fp, "r1", asset_hash=None, mtime_ns=mtime)
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
survivors = sync_references_with_filesystem(
session, "models", collect_existing_paths=True,
)
Expand All @@ -240,7 +240,7 @@ def test_hashed_asset_prunes_missing_refs_when_one_is_ok(session, temp_dir):
session.add(ref_gone)
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
sync_references_with_filesystem(session, "models")
session.commit()

Expand All @@ -255,7 +255,7 @@ def test_hashed_asset_all_missing_keeps_refs(session, temp_dir):
_make_asset(session, "h1", fp, "r1", asset_hash="blake3:aaa", mtime_ns=999)
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
sync_references_with_filesystem(session, "models")
session.commit()

Expand All @@ -272,7 +272,7 @@ def test_missing_tag_added_when_all_refs_gone(session, temp_dir):
_make_asset(session, "h1", fp, "r1", asset_hash="blake3:aaa", mtime_ns=999)
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
sync_references_with_filesystem(
session, "models", update_missing_tags=True,
)
Expand All @@ -295,7 +295,7 @@ def test_missing_tag_removed_when_ref_ok(session, temp_dir):
))
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
sync_references_with_filesystem(
session, "models", update_missing_tags=True,
)
Expand All @@ -313,7 +313,7 @@ def test_missing_tags_not_touched_when_flag_false(session, temp_dir):
_make_asset(session, "h1", fp, "r1", asset_hash="blake3:aaa", mtime_ns=999)
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
sync_references_with_filesystem(
session, "models", update_missing_tags=False,
)
Expand All @@ -329,7 +329,7 @@ def test_returns_none_when_collect_false(session, temp_dir):
_make_asset(session, "a1", fp, "r1", asset_hash="blake3:abc", mtime_ns=mtime)
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
result = sync_references_with_filesystem(
session, "models", collect_existing_paths=False,
)
Expand All @@ -338,7 +338,7 @@ def test_returns_none_when_collect_false(session, temp_dir):


def test_returns_empty_set_for_no_prefixes(session):
with patch("app.assets.scanner.get_prefixes_for_root", return_value=[]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[]):
result = sync_references_with_filesystem(
session, "models", collect_existing_paths=True,
)
Expand All @@ -348,7 +348,7 @@ def test_returns_empty_set_for_no_prefixes(session):

def test_no_references_is_noop(session, temp_dir):
"""No crash and no side effects when there are no references."""
with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
survivors = sync_references_with_filesystem(
session, "models", collect_existing_paths=True,
)
Expand Down Expand Up @@ -388,7 +388,7 @@ def test_sync_does_not_resurrect_soft_deleted_ref(session, temp_dir):
_soft_delete_ref(session, "r1")
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
sync_references_with_filesystem(session, "models")
session.commit()

Expand Down Expand Up @@ -472,7 +472,7 @@ def test_sync_ignores_soft_deleted_seed_asset(session, temp_dir):
_soft_delete_ref(session, "r1")
session.commit()

with patch("app.assets.scanner.get_prefixes_for_root", return_value=[str(temp_dir)]):
with patch("app.assets.scanner.get_scan_prefixes_for_root", return_value=[str(temp_dir)]):
sync_references_with_filesystem(session, "models")
session.commit()

Expand Down
Loading
Loading