Move snapshot data to ComfyUI user directory
Some checks failed
Publish to ComfyUI Registry / Publish Custom Node to Registry (push) Has been cancelled
Some checks failed
Publish to ComfyUI Registry / Publish Custom Node to Registry (push) Has been cancelled
Store snapshots and profiles under <user_dir>/snapshot_manager/ instead of <extension>/data/ so user data survives extension updates and reinstalls. Automatically migrates existing data on first load. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-snapshot-manager"
|
name = "comfyui-snapshot-manager"
|
||||||
description = "Automatically snapshots workflow state with a sidebar to browse and restore previous versions."
|
description = "Automatically snapshots workflow state with a sidebar to browse and restore previous versions."
|
||||||
version = "3.0.0"
|
version = "3.1.0"
|
||||||
license = {text = "MIT"}
|
license = {text = "MIT"}
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Filesystem storage layer for workflow snapshots.
|
Filesystem storage layer for workflow snapshots.
|
||||||
|
|
||||||
Stores each snapshot as an individual JSON file under:
|
Stores each snapshot as an individual JSON file under:
|
||||||
<extension_dir>/data/snapshots/<encoded_workflow_key>/<id>.json
|
<user_dir>/snapshot_manager/snapshots/<encoded_workflow_key>/<id>.json
|
||||||
|
|
||||||
Workflow keys are percent-encoded for filesystem safety.
|
Workflow keys are percent-encoded for filesystem safety.
|
||||||
|
|
||||||
@@ -12,9 +12,22 @@ operations. Only get_full_record() reads a file from disk after warm-up.
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
_DATA_DIR = os.path.join(os.path.dirname(__file__), "data", "snapshots")
|
# ─── Data directory resolution ───────────────────────────────────────
|
||||||
|
# Prefer ComfyUI's persistent user directory; fall back to extension-local
|
||||||
|
# paths when running outside ComfyUI (e.g. tests).
|
||||||
|
|
||||||
|
_OLD_DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import folder_paths
|
||||||
|
_USER_SM_DIR = os.path.join(folder_paths.get_user_directory(), "snapshot_manager")
|
||||||
|
except Exception:
|
||||||
|
_USER_SM_DIR = os.path.join(os.path.dirname(__file__), "data")
|
||||||
|
|
||||||
|
_DATA_DIR = os.path.join(_USER_SM_DIR, "snapshots")
|
||||||
|
|
||||||
# ─── In-memory metadata cache ────────────────────────────────────────
|
# ─── In-memory metadata cache ────────────────────────────────────────
|
||||||
# Maps workflow_key -> list of metadata dicts (sorted by timestamp asc).
|
# Maps workflow_key -> list of metadata dicts (sorted by timestamp asc).
|
||||||
@@ -250,9 +263,9 @@ def prune(workflow_key, max_snapshots, source=None, protected_ids=None):
|
|||||||
|
|
||||||
|
|
||||||
# ─── Profile Storage ─────────────────────────────────────────────────
|
# ─── Profile Storage ─────────────────────────────────────────────────
|
||||||
# Profiles are stored as individual JSON files under data/profiles/<id>.json
|
# Profiles are stored as individual JSON files under snapshot_manager/profiles/<id>.json
|
||||||
|
|
||||||
_PROFILES_DIR = os.path.join(os.path.dirname(__file__), "data", "profiles")
|
_PROFILES_DIR = os.path.join(_USER_SM_DIR, "profiles")
|
||||||
_profile_cache = None # list of profile dicts, or None if not loaded
|
_profile_cache = None # list of profile dicts, or None if not loaded
|
||||||
|
|
||||||
|
|
||||||
@@ -340,3 +353,77 @@ def profile_update(profile_id, fields):
|
|||||||
json.dump(profile, f, separators=(",", ":"))
|
json.dump(profile, f, separators=(",", ":"))
|
||||||
_invalidate_profile_cache()
|
_invalidate_profile_cache()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Migration from old extension-local data ─────────────────────────
|
||||||
|
|
||||||
|
def _migrate_old_data():
|
||||||
|
"""Move data from the old <extension>/data/ location to the new user directory.
|
||||||
|
|
||||||
|
Only runs when the old directory exists, has content, and the new location
|
||||||
|
differs from the old one (i.e. we're actually inside ComfyUI).
|
||||||
|
"""
|
||||||
|
old_snapshots = os.path.join(_OLD_DATA_DIR, "snapshots")
|
||||||
|
old_profiles = os.path.join(_OLD_DATA_DIR, "profiles")
|
||||||
|
|
||||||
|
# Nothing to migrate if old data dir doesn't exist or paths are the same
|
||||||
|
if os.path.normpath(_OLD_DATA_DIR) == os.path.normpath(_USER_SM_DIR):
|
||||||
|
return
|
||||||
|
if not os.path.isdir(_OLD_DATA_DIR):
|
||||||
|
return
|
||||||
|
|
||||||
|
migrated_anything = False
|
||||||
|
|
||||||
|
# Migrate snapshot workflow directories
|
||||||
|
if os.path.isdir(old_snapshots):
|
||||||
|
os.makedirs(_DATA_DIR, exist_ok=True)
|
||||||
|
for name in os.listdir(old_snapshots):
|
||||||
|
src = os.path.join(old_snapshots, name)
|
||||||
|
dst = os.path.join(_DATA_DIR, name)
|
||||||
|
if not os.path.isdir(src):
|
||||||
|
continue
|
||||||
|
if os.path.exists(dst):
|
||||||
|
# Merge: move individual files that don't already exist
|
||||||
|
for fname in os.listdir(src):
|
||||||
|
s = os.path.join(src, fname)
|
||||||
|
d = os.path.join(dst, fname)
|
||||||
|
if not os.path.exists(d):
|
||||||
|
shutil.move(s, d)
|
||||||
|
# Remove source dir if now empty
|
||||||
|
try:
|
||||||
|
os.rmdir(src)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
shutil.move(src, dst)
|
||||||
|
migrated_anything = True
|
||||||
|
|
||||||
|
# Migrate profile files
|
||||||
|
if os.path.isdir(old_profiles):
|
||||||
|
os.makedirs(_PROFILES_DIR, exist_ok=True)
|
||||||
|
for fname in os.listdir(old_profiles):
|
||||||
|
if not fname.endswith(".json"):
|
||||||
|
continue
|
||||||
|
src = os.path.join(old_profiles, fname)
|
||||||
|
dst = os.path.join(_PROFILES_DIR, fname)
|
||||||
|
if not os.path.exists(dst):
|
||||||
|
shutil.move(src, dst)
|
||||||
|
else:
|
||||||
|
os.remove(src)
|
||||||
|
migrated_anything = True
|
||||||
|
|
||||||
|
if migrated_anything:
|
||||||
|
# Clean up old directories if empty
|
||||||
|
for d in (old_snapshots, old_profiles, _OLD_DATA_DIR):
|
||||||
|
try:
|
||||||
|
if os.path.isdir(d) and not os.listdir(d):
|
||||||
|
os.rmdir(d)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
print("[Snapshot Manager] Migrated data to", _USER_SM_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
_migrate_old_data()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Snapshot Manager] Migration failed, old data preserved: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user